@code-pushup/utils 0.47.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1156 -1209
- package/package.json +8 -7
- package/src/index.d.ts +4 -2
- package/src/lib/merge-configs.d.ts +2 -0
- package/src/lib/reports/constants.d.ts +2 -27
- package/src/lib/reports/formatting.d.ts +4 -4
- package/src/lib/reports/generate-md-report-categoy-section.d.ts +6 -4
- package/src/lib/reports/generate-md-report.d.ts +9 -8
- package/src/lib/reports/generate-md-reports-diff.d.ts +1 -1
- package/src/lib/reports/load-report.d.ts +6 -0
- package/src/lib/reports/log-stdout-summary.d.ts +2 -0
- package/src/lib/reports/sorting.d.ts +5 -1
- package/src/lib/reports/utils.d.ts +18 -12
- package/src/lib/text-formats/constants.d.ts +8 -0
- package/src/lib/text-formats/index.d.ts +9 -40
- package/src/lib/transform.d.ts +0 -1
- package/src/lib/text-formats/md/font-style.d.ts +0 -4
- package/src/lib/text-formats/md/headline.d.ts +0 -14
- package/src/lib/text-formats/md/image.d.ts +0 -1
- package/src/lib/text-formats/md/link.d.ts +0 -1
- package/src/lib/text-formats/md/list.d.ts +0 -7
- package/src/lib/text-formats/md/paragraphs.d.ts +0 -1
- package/src/lib/text-formats/md/section.d.ts +0 -2
- package/src/lib/text-formats/md/table.d.ts +0 -9
- package/src/lib/text-formats/types.d.ts +0 -1
package/index.js
CHANGED
|
@@ -739,11 +739,262 @@ function comparePairs(pairs, equalsFn) {
|
|
|
739
739
|
import { spawn } from "node:child_process";
|
|
740
740
|
|
|
741
741
|
// packages/utils/src/lib/reports/utils.ts
|
|
742
|
-
import
|
|
742
|
+
import ansis from "ansis";
|
|
743
|
+
import { md } from "build-md";
|
|
744
|
+
|
|
745
|
+
// packages/utils/src/lib/reports/constants.ts
|
|
746
|
+
var TERMINAL_WIDTH = 80;
|
|
747
|
+
var SCORE_COLOR_RANGE = {
|
|
748
|
+
GREEN_MIN: 0.9,
|
|
749
|
+
YELLOW_MIN: 0.5
|
|
750
|
+
};
|
|
751
|
+
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
752
|
+
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
753
|
+
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
754
|
+
var REPORT_HEADLINE_TEXT = "Code PushUp Report";
|
|
755
|
+
var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
|
|
756
|
+
"Category",
|
|
757
|
+
"Score",
|
|
758
|
+
"Audits"
|
|
759
|
+
];
|
|
760
|
+
|
|
761
|
+
// packages/utils/src/lib/reports/utils.ts
|
|
762
|
+
function formatReportScore(score) {
|
|
763
|
+
const scaledScore = score * 100;
|
|
764
|
+
const roundedScore = Math.round(scaledScore);
|
|
765
|
+
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
766
|
+
}
|
|
767
|
+
function formatScoreWithColor(score, options) {
|
|
768
|
+
const styledNumber = options?.skipBold ? formatReportScore(score) : md.bold(formatReportScore(score));
|
|
769
|
+
return md`${scoreMarker(score)} ${styledNumber}`;
|
|
770
|
+
}
|
|
771
|
+
var MARKERS = {
|
|
772
|
+
circle: {
|
|
773
|
+
red: "\u{1F534}",
|
|
774
|
+
yellow: "\u{1F7E1}",
|
|
775
|
+
green: "\u{1F7E2}"
|
|
776
|
+
},
|
|
777
|
+
square: {
|
|
778
|
+
red: "\u{1F7E5}",
|
|
779
|
+
yellow: "\u{1F7E8}",
|
|
780
|
+
green: "\u{1F7E9}"
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
function scoreMarker(score, markerType = "circle") {
|
|
784
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
785
|
+
return MARKERS[markerType].green;
|
|
786
|
+
}
|
|
787
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
788
|
+
return MARKERS[markerType].yellow;
|
|
789
|
+
}
|
|
790
|
+
return MARKERS[markerType].red;
|
|
791
|
+
}
|
|
792
|
+
function getDiffMarker(diff) {
|
|
793
|
+
if (diff > 0) {
|
|
794
|
+
return "\u2191";
|
|
795
|
+
}
|
|
796
|
+
if (diff < 0) {
|
|
797
|
+
return "\u2193";
|
|
798
|
+
}
|
|
799
|
+
return "";
|
|
800
|
+
}
|
|
801
|
+
function colorByScoreDiff(text, diff) {
|
|
802
|
+
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
803
|
+
return shieldsBadge(text, color);
|
|
804
|
+
}
|
|
805
|
+
function shieldsBadge(text, color) {
|
|
806
|
+
return md.image(
|
|
807
|
+
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
808
|
+
text
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
function formatDiffNumber(diff) {
|
|
812
|
+
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
813
|
+
const sign = diff < 0 ? "\u2212" : "+";
|
|
814
|
+
return `${sign}${number}`;
|
|
815
|
+
}
|
|
816
|
+
function severityMarker(severity) {
|
|
817
|
+
if (severity === "error") {
|
|
818
|
+
return "\u{1F6A8}";
|
|
819
|
+
}
|
|
820
|
+
if (severity === "warning") {
|
|
821
|
+
return "\u26A0\uFE0F";
|
|
822
|
+
}
|
|
823
|
+
return "\u2139\uFE0F";
|
|
824
|
+
}
|
|
825
|
+
function formatScoreChange(diff) {
|
|
826
|
+
const marker = getDiffMarker(diff);
|
|
827
|
+
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
828
|
+
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
829
|
+
}
|
|
830
|
+
function formatValueChange({
|
|
831
|
+
values,
|
|
832
|
+
scores
|
|
833
|
+
}) {
|
|
834
|
+
const marker = getDiffMarker(values.diff);
|
|
835
|
+
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
836
|
+
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
837
|
+
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
838
|
+
}
|
|
839
|
+
function calcDuration(start, stop) {
|
|
840
|
+
return Math.round((stop ?? performance.now()) - start);
|
|
841
|
+
}
|
|
842
|
+
function countCategoryAudits(refs, plugins) {
|
|
843
|
+
const groupLookup = plugins.reduce(
|
|
844
|
+
(lookup, plugin) => {
|
|
845
|
+
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
846
|
+
return lookup;
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
...lookup,
|
|
850
|
+
[plugin.slug]: Object.fromEntries(
|
|
851
|
+
plugin.groups.map((group) => [group.slug, group])
|
|
852
|
+
)
|
|
853
|
+
};
|
|
854
|
+
},
|
|
855
|
+
{}
|
|
856
|
+
);
|
|
857
|
+
return refs.reduce((acc, ref) => {
|
|
858
|
+
if (ref.type === "group") {
|
|
859
|
+
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
860
|
+
return acc + (groupRefs?.length ?? 0);
|
|
861
|
+
}
|
|
862
|
+
return acc + 1;
|
|
863
|
+
}, 0);
|
|
864
|
+
}
|
|
865
|
+
function compareCategoryAuditsAndGroups(a, b) {
|
|
866
|
+
if (a.weight !== b.weight) {
|
|
867
|
+
return b.weight - a.weight;
|
|
868
|
+
}
|
|
869
|
+
if (a.score !== b.score) {
|
|
870
|
+
return a.score - b.score;
|
|
871
|
+
}
|
|
872
|
+
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
873
|
+
return b.value - a.value;
|
|
874
|
+
}
|
|
875
|
+
return a.title.localeCompare(b.title);
|
|
876
|
+
}
|
|
877
|
+
function compareAudits(a, b) {
|
|
878
|
+
if (a.score !== b.score) {
|
|
879
|
+
return a.score - b.score;
|
|
880
|
+
}
|
|
881
|
+
if (a.value !== b.value) {
|
|
882
|
+
return b.value - a.value;
|
|
883
|
+
}
|
|
884
|
+
return a.title.localeCompare(b.title);
|
|
885
|
+
}
|
|
886
|
+
function compareIssueSeverity(severity1, severity2) {
|
|
887
|
+
const levels = {
|
|
888
|
+
info: 0,
|
|
889
|
+
warning: 1,
|
|
890
|
+
error: 2
|
|
891
|
+
};
|
|
892
|
+
return levels[severity1] - levels[severity2];
|
|
893
|
+
}
|
|
894
|
+
function throwIsNotPresentError(itemName, presentPlace) {
|
|
895
|
+
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
896
|
+
}
|
|
897
|
+
function getPluginNameFromSlug(slug, plugins) {
|
|
898
|
+
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
899
|
+
}
|
|
900
|
+
function compareIssues(a, b) {
|
|
901
|
+
if (a.severity !== b.severity) {
|
|
902
|
+
return -compareIssueSeverity(a.severity, b.severity);
|
|
903
|
+
}
|
|
904
|
+
if (!a.source && b.source) {
|
|
905
|
+
return -1;
|
|
906
|
+
}
|
|
907
|
+
if (a.source && !b.source) {
|
|
908
|
+
return 1;
|
|
909
|
+
}
|
|
910
|
+
if (a.source?.file !== b.source?.file) {
|
|
911
|
+
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
912
|
+
}
|
|
913
|
+
if (!a.source?.position && b.source?.position) {
|
|
914
|
+
return -1;
|
|
915
|
+
}
|
|
916
|
+
if (a.source?.position && !b.source?.position) {
|
|
917
|
+
return 1;
|
|
918
|
+
}
|
|
919
|
+
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
920
|
+
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
921
|
+
}
|
|
922
|
+
return 0;
|
|
923
|
+
}
|
|
924
|
+
function applyScoreColor({ score, text }, style = ansis) {
|
|
925
|
+
const formattedScore = text ?? formatReportScore(score);
|
|
926
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
927
|
+
return text ? style.green(formattedScore) : style.bold(style.green(formattedScore));
|
|
928
|
+
}
|
|
929
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
930
|
+
return text ? style.yellow(formattedScore) : style.bold(style.yellow(formattedScore));
|
|
931
|
+
}
|
|
932
|
+
return text ? style.red(formattedScore) : style.bold(style.red(formattedScore));
|
|
933
|
+
}
|
|
934
|
+
function targetScoreIcon(score, targetScore, options = {}) {
|
|
935
|
+
if (targetScore != null) {
|
|
936
|
+
const {
|
|
937
|
+
passIcon = "\u2705",
|
|
938
|
+
failIcon = "\u274C",
|
|
939
|
+
prefix = "",
|
|
940
|
+
postfix = ""
|
|
941
|
+
} = options;
|
|
942
|
+
if (score >= targetScore) {
|
|
943
|
+
return `${prefix}${passIcon}${postfix}`;
|
|
944
|
+
}
|
|
945
|
+
return `${prefix}${failIcon}${postfix}`;
|
|
946
|
+
}
|
|
947
|
+
return "";
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// packages/utils/src/lib/execute-process.ts
|
|
951
|
+
var ProcessError = class extends Error {
|
|
952
|
+
code;
|
|
953
|
+
stderr;
|
|
954
|
+
stdout;
|
|
955
|
+
constructor(result) {
|
|
956
|
+
super(result.stderr);
|
|
957
|
+
this.code = result.code;
|
|
958
|
+
this.stderr = result.stderr;
|
|
959
|
+
this.stdout = result.stdout;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
function executeProcess(cfg) {
|
|
963
|
+
const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
|
|
964
|
+
const { onStdout, onError, onComplete } = observer ?? {};
|
|
965
|
+
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
966
|
+
const start = performance.now();
|
|
967
|
+
return new Promise((resolve, reject) => {
|
|
968
|
+
const process2 = spawn(command, args, { cwd, shell: true });
|
|
969
|
+
let stdout = "";
|
|
970
|
+
let stderr = "";
|
|
971
|
+
process2.stdout.on("data", (data) => {
|
|
972
|
+
stdout += String(data);
|
|
973
|
+
onStdout?.(String(data));
|
|
974
|
+
});
|
|
975
|
+
process2.stderr.on("data", (data) => {
|
|
976
|
+
stderr += String(data);
|
|
977
|
+
});
|
|
978
|
+
process2.on("error", (err) => {
|
|
979
|
+
stderr += err.toString();
|
|
980
|
+
});
|
|
981
|
+
process2.on("close", (code2) => {
|
|
982
|
+
const timings = { date, duration: calcDuration(start) };
|
|
983
|
+
if (code2 === 0 || ignoreExitCode) {
|
|
984
|
+
onComplete?.();
|
|
985
|
+
resolve({ code: code2, stdout, stderr, ...timings });
|
|
986
|
+
} else {
|
|
987
|
+
const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
|
|
988
|
+
onError?.(errorMsg);
|
|
989
|
+
reject(errorMsg);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
}
|
|
743
994
|
|
|
744
995
|
// packages/utils/src/lib/file-system.ts
|
|
996
|
+
import { bold, gray } from "ansis";
|
|
745
997
|
import { bundleRequire } from "bundle-require";
|
|
746
|
-
import chalk2 from "chalk";
|
|
747
998
|
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
748
999
|
import { join } from "node:path";
|
|
749
1000
|
|
|
@@ -836,55 +1087,7 @@ function isPromiseRejectedResult(result) {
|
|
|
836
1087
|
// packages/utils/src/lib/logging.ts
|
|
837
1088
|
import isaacs_cliui from "@isaacs/cliui";
|
|
838
1089
|
import { cliui } from "@poppinss/cliui";
|
|
839
|
-
import
|
|
840
|
-
|
|
841
|
-
// packages/utils/src/lib/reports/constants.ts
|
|
842
|
-
var TERMINAL_WIDTH = 80;
|
|
843
|
-
var SCORE_COLOR_RANGE = {
|
|
844
|
-
GREEN_MIN: 0.9,
|
|
845
|
-
YELLOW_MIN: 0.5
|
|
846
|
-
};
|
|
847
|
-
var CATEGORIES_TITLE = "\u{1F3F7} Categories";
|
|
848
|
-
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
849
|
-
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
850
|
-
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
851
|
-
var reportHeadlineText = "Code PushUp Report";
|
|
852
|
-
var reportOverviewTableHeaders = [
|
|
853
|
-
{
|
|
854
|
-
key: "category",
|
|
855
|
-
label: "\u{1F3F7} Category",
|
|
856
|
-
align: "left"
|
|
857
|
-
},
|
|
858
|
-
{
|
|
859
|
-
key: "score",
|
|
860
|
-
label: "\u2B50 Score"
|
|
861
|
-
},
|
|
862
|
-
{
|
|
863
|
-
key: "audits",
|
|
864
|
-
label: "\u{1F6E1} Audits"
|
|
865
|
-
}
|
|
866
|
-
];
|
|
867
|
-
var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
|
|
868
|
-
var issuesTableHeadings = [
|
|
869
|
-
{
|
|
870
|
-
key: "severity",
|
|
871
|
-
label: "Severity"
|
|
872
|
-
},
|
|
873
|
-
{
|
|
874
|
-
key: "message",
|
|
875
|
-
label: "Message"
|
|
876
|
-
},
|
|
877
|
-
{
|
|
878
|
-
key: "file",
|
|
879
|
-
label: "Source file"
|
|
880
|
-
},
|
|
881
|
-
{
|
|
882
|
-
key: "line",
|
|
883
|
-
label: "Line(s)"
|
|
884
|
-
}
|
|
885
|
-
];
|
|
886
|
-
|
|
887
|
-
// packages/utils/src/lib/logging.ts
|
|
1090
|
+
import { underline } from "ansis";
|
|
888
1091
|
var singletonUiInstance;
|
|
889
1092
|
function ui() {
|
|
890
1093
|
if (singletonUiInstance === void 0) {
|
|
@@ -908,7 +1111,7 @@ function logListItem(args) {
|
|
|
908
1111
|
singletonUiInstance?.logger.log(content);
|
|
909
1112
|
}
|
|
910
1113
|
function link(text) {
|
|
911
|
-
return
|
|
1114
|
+
return underline.blueBright(text);
|
|
912
1115
|
}
|
|
913
1116
|
|
|
914
1117
|
// packages/utils/src/lib/log-results.ts
|
|
@@ -988,10 +1191,10 @@ async function removeDirectoryIfExists(dir) {
|
|
|
988
1191
|
function logMultipleFileResults(fileResults, messagePrefix) {
|
|
989
1192
|
const succeededTransform = (result) => {
|
|
990
1193
|
const [fileName, size] = result.value;
|
|
991
|
-
const formattedSize = size ? ` (${
|
|
992
|
-
return `- ${
|
|
1194
|
+
const formattedSize = size ? ` (${gray(formatBytes(size))})` : "";
|
|
1195
|
+
return `- ${bold(fileName)}${formattedSize}`;
|
|
993
1196
|
};
|
|
994
|
-
const failedTransform = (result) => `- ${
|
|
1197
|
+
const failedTransform = (result) => `- ${bold(result.reason)}`;
|
|
995
1198
|
logMultipleResults(
|
|
996
1199
|
fileResults,
|
|
997
1200
|
messagePrefix,
|
|
@@ -1031,46 +1234,25 @@ async function crawlFileSystem(options) {
|
|
|
1031
1234
|
return resultsNestedArray.flat();
|
|
1032
1235
|
}
|
|
1033
1236
|
function findLineNumberInText(content, pattern) {
|
|
1034
|
-
const
|
|
1035
|
-
const lineNumber =
|
|
1237
|
+
const lines = content.split(/\r?\n/);
|
|
1238
|
+
const lineNumber = lines.findIndex((line) => line.includes(pattern)) + 1;
|
|
1036
1239
|
return lineNumber === 0 ? null : lineNumber;
|
|
1037
1240
|
}
|
|
1038
1241
|
function filePathToCliArg(path) {
|
|
1039
1242
|
return `"${path}"`;
|
|
1040
1243
|
}
|
|
1041
1244
|
|
|
1042
|
-
// packages/utils/src/lib/
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
function details(title, content, cfg = { open: false }) {
|
|
1049
|
-
return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
|
|
1050
|
-
NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
|
|
1051
|
-
// ⚠️ The blank line ensure Markdown in content is rendered correctly.
|
|
1052
|
-
NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
|
|
1053
|
-
NEW_LINE}`;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// packages/utils/src/lib/text-formats/html/font-style.ts
|
|
1057
|
-
var boldElement = "b";
|
|
1058
|
-
function bold(text) {
|
|
1059
|
-
return `<${boldElement}>${text}</${boldElement}>`;
|
|
1060
|
-
}
|
|
1061
|
-
var italicElement = "i";
|
|
1062
|
-
function italic(text) {
|
|
1063
|
-
return `<${italicElement}>${text}</${italicElement}>`;
|
|
1064
|
-
}
|
|
1065
|
-
var codeElement = "code";
|
|
1066
|
-
function code(text) {
|
|
1067
|
-
return `<${codeElement}>${text}</${codeElement}>`;
|
|
1245
|
+
// packages/utils/src/lib/filter.ts
|
|
1246
|
+
function filterItemRefsBy(items, refFilterFn) {
|
|
1247
|
+
return items.map((item) => ({
|
|
1248
|
+
...item,
|
|
1249
|
+
refs: item.refs.filter(refFilterFn)
|
|
1250
|
+
})).filter((item) => item.refs.length);
|
|
1068
1251
|
}
|
|
1069
1252
|
|
|
1070
|
-
// packages/utils/src/lib/
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1253
|
+
// packages/utils/src/lib/git/git.ts
|
|
1254
|
+
import { isAbsolute, join as join2, relative } from "node:path";
|
|
1255
|
+
import { simpleGit } from "simple-git";
|
|
1074
1256
|
|
|
1075
1257
|
// packages/utils/src/lib/transform.ts
|
|
1076
1258
|
import { platform } from "node:os";
|
|
@@ -1121,6 +1303,12 @@ function objectToCliArgs(params) {
|
|
|
1121
1303
|
if (Array.isArray(value)) {
|
|
1122
1304
|
return value.map((v) => `${prefix}${key}="${v}"`);
|
|
1123
1305
|
}
|
|
1306
|
+
if (typeof value === "object") {
|
|
1307
|
+
return Object.entries(value).flatMap(
|
|
1308
|
+
// transform nested objects to the dot notation `key.subkey`
|
|
1309
|
+
([k, v]) => objectToCliArgs({ [`${key}.${k}`]: v })
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1124
1312
|
if (typeof value === "string") {
|
|
1125
1313
|
return [`${prefix}${key}="${value}"`];
|
|
1126
1314
|
}
|
|
@@ -1151,11 +1339,6 @@ function capitalize(text) {
|
|
|
1151
1339
|
1
|
|
1152
1340
|
)}`;
|
|
1153
1341
|
}
|
|
1154
|
-
function apostrophize(text, upperCase) {
|
|
1155
|
-
const lastCharMatch = text.match(/(\w)\W*$/);
|
|
1156
|
-
const lastChar = lastCharMatch?.[1] ?? "";
|
|
1157
|
-
return `${text}'${lastChar.toLocaleLowerCase() === "s" ? "" : upperCase ? "S" : "s"}`;
|
|
1158
|
-
}
|
|
1159
1342
|
function toNumberPrecision(value, decimalPlaces) {
|
|
1160
1343
|
return Number(
|
|
1161
1344
|
`${Math.round(
|
|
@@ -1176,1088 +1359,908 @@ function toOrdinal(value) {
|
|
|
1176
1359
|
return `${value}th`;
|
|
1177
1360
|
}
|
|
1178
1361
|
|
|
1179
|
-
// packages/utils/src/lib/
|
|
1180
|
-
function
|
|
1181
|
-
|
|
1182
|
-
throw new TypeError(
|
|
1183
|
-
"Column can`t be object when rows are primitive values"
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
return rows.map((row) => {
|
|
1187
|
-
if (Array.isArray(row)) {
|
|
1188
|
-
return row.map(String);
|
|
1189
|
-
}
|
|
1190
|
-
const objectRow = row;
|
|
1191
|
-
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1192
|
-
return Object.values(objectRow).map(
|
|
1193
|
-
(value) => value == null ? "" : String(value)
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
return columns.map(
|
|
1197
|
-
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1198
|
-
);
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
function columnsToStringArray({
|
|
1202
|
-
rows,
|
|
1203
|
-
columns = []
|
|
1204
|
-
}) {
|
|
1205
|
-
const firstRow = rows.at(0);
|
|
1206
|
-
const primitiveRows = Array.isArray(firstRow);
|
|
1207
|
-
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1208
|
-
throw new Error("invalid union type. Caught by model parsing.");
|
|
1209
|
-
}
|
|
1210
|
-
if (columns.length === 0) {
|
|
1211
|
-
if (Array.isArray(firstRow)) {
|
|
1212
|
-
return firstRow.map((_, idx) => String(idx));
|
|
1213
|
-
}
|
|
1214
|
-
return Object.keys(firstRow);
|
|
1215
|
-
}
|
|
1216
|
-
if (typeof columns.at(0) === "string") {
|
|
1217
|
-
return columns.map(String);
|
|
1218
|
-
}
|
|
1219
|
-
const cols = columns;
|
|
1220
|
-
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1362
|
+
// packages/utils/src/lib/git/git.ts
|
|
1363
|
+
function getGitRoot(git = simpleGit()) {
|
|
1364
|
+
return git.revparse("--show-toplevel");
|
|
1221
1365
|
}
|
|
1222
|
-
function
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
} else if (typeof column === "object") {
|
|
1227
|
-
return column.align ?? "center";
|
|
1228
|
-
} else {
|
|
1229
|
-
return "center";
|
|
1230
|
-
}
|
|
1366
|
+
function formatGitPath(path, gitRoot) {
|
|
1367
|
+
const absolutePath = isAbsolute(path) ? path : join2(process.cwd(), path);
|
|
1368
|
+
const relativePath = relative(gitRoot, absolutePath);
|
|
1369
|
+
return toUnixPath(relativePath);
|
|
1231
1370
|
}
|
|
1232
|
-
function
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
return "center";
|
|
1236
|
-
} else if (typeof column === "string") {
|
|
1237
|
-
return column;
|
|
1238
|
-
} else if (typeof column === "object") {
|
|
1239
|
-
return column.align ?? "center";
|
|
1240
|
-
} else {
|
|
1241
|
-
return "center";
|
|
1242
|
-
}
|
|
1371
|
+
async function toGitPath(path, git = simpleGit()) {
|
|
1372
|
+
const gitRoot = await getGitRoot(git);
|
|
1373
|
+
return formatGitPath(path, gitRoot);
|
|
1243
1374
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1375
|
+
var GitStatusError = class _GitStatusError extends Error {
|
|
1376
|
+
static ignoredProps = /* @__PURE__ */ new Set(["current", "tracking"]);
|
|
1377
|
+
static getReducedStatus(status) {
|
|
1378
|
+
return Object.fromEntries(
|
|
1379
|
+
Object.entries(status).filter(([key]) => !this.ignoredProps.has(key)).filter(
|
|
1380
|
+
(entry) => {
|
|
1381
|
+
const value = entry[1];
|
|
1382
|
+
if (value == null) {
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
1386
|
+
return false;
|
|
1387
|
+
}
|
|
1388
|
+
if (typeof value === "number" && value === 0) {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
return !(typeof value === "boolean" && !value);
|
|
1392
|
+
}
|
|
1393
|
+
)
|
|
1253
1394
|
);
|
|
1254
1395
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
)
|
|
1396
|
+
constructor(status) {
|
|
1397
|
+
super(
|
|
1398
|
+
`Working directory needs to be clean before we you can proceed. Commit your local changes or stash them:
|
|
1399
|
+
${JSON.stringify(
|
|
1400
|
+
_GitStatusError.getReducedStatus(status),
|
|
1401
|
+
null,
|
|
1402
|
+
2
|
|
1403
|
+
)}`
|
|
1263
1404
|
);
|
|
1264
1405
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
return `<${elem}>${content}</${elem}>${NEW_LINE}`;
|
|
1271
|
-
}
|
|
1272
|
-
function wrapRow(content) {
|
|
1273
|
-
const elem = "tr";
|
|
1274
|
-
return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
|
|
1275
|
-
}
|
|
1276
|
-
function table(tableData) {
|
|
1277
|
-
if (tableData.rows.length === 0) {
|
|
1278
|
-
throw new Error("Data can't be empty");
|
|
1406
|
+
};
|
|
1407
|
+
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1408
|
+
const status = await git.status(["-s"]);
|
|
1409
|
+
if (status.files.length > 0) {
|
|
1410
|
+
throw new GitStatusError(status);
|
|
1279
1411
|
}
|
|
1280
|
-
const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
|
|
1281
|
-
const tableHeaderRow = wrapRow(tableHeaderCols);
|
|
1282
|
-
const tableBody = rowToStringArray(tableData).map((arr) => {
|
|
1283
|
-
const columns = arr.map((s) => wrap("td", s)).join("");
|
|
1284
|
-
return wrapRow(columns);
|
|
1285
|
-
}).join("");
|
|
1286
|
-
return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
// packages/utils/src/lib/text-formats/md/font-style.ts
|
|
1290
|
-
var boldWrap = "**";
|
|
1291
|
-
function bold2(text) {
|
|
1292
|
-
return `${boldWrap}${text}${boldWrap}`;
|
|
1293
1412
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
var codeWrap = "`";
|
|
1303
|
-
function code2(text) {
|
|
1304
|
-
return `${codeWrap}${text}${codeWrap}`;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// packages/utils/src/lib/text-formats/md/headline.ts
|
|
1308
|
-
function headline(text, hierarchy = 1) {
|
|
1309
|
-
return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
|
|
1310
|
-
}
|
|
1311
|
-
function h(text, hierarchy = 1) {
|
|
1312
|
-
return headline(text, hierarchy);
|
|
1313
|
-
}
|
|
1314
|
-
function h1(text) {
|
|
1315
|
-
return headline(text, 1);
|
|
1316
|
-
}
|
|
1317
|
-
function h2(text) {
|
|
1318
|
-
return headline(text, 2);
|
|
1319
|
-
}
|
|
1320
|
-
function h3(text) {
|
|
1321
|
-
return headline(text, 3);
|
|
1322
|
-
}
|
|
1323
|
-
function h4(text) {
|
|
1324
|
-
return headline(text, 4);
|
|
1325
|
-
}
|
|
1326
|
-
function h5(text) {
|
|
1327
|
-
return headline(text, 5);
|
|
1328
|
-
}
|
|
1329
|
-
function h6(text) {
|
|
1330
|
-
return headline(text, 6);
|
|
1413
|
+
async function safeCheckout(branchOrHash, forceCleanStatus = false, git = simpleGit()) {
|
|
1414
|
+
if (forceCleanStatus) {
|
|
1415
|
+
await git.raw(["reset", "--hard"]);
|
|
1416
|
+
await git.clean(["f", "d"]);
|
|
1417
|
+
ui().logger.info(`git status cleaned`);
|
|
1418
|
+
}
|
|
1419
|
+
await guardAgainstLocalChanges(git);
|
|
1420
|
+
await git.checkout(branchOrHash);
|
|
1331
1421
|
}
|
|
1332
1422
|
|
|
1333
|
-
// packages/utils/src/lib/
|
|
1334
|
-
|
|
1335
|
-
return ``;
|
|
1336
|
-
}
|
|
1423
|
+
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1424
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1337
1425
|
|
|
1338
|
-
// packages/utils/src/lib/
|
|
1339
|
-
|
|
1340
|
-
|
|
1426
|
+
// packages/utils/src/lib/semver.ts
|
|
1427
|
+
import { rcompare, valid } from "semver";
|
|
1428
|
+
function normalizeSemver(semverString) {
|
|
1429
|
+
if (semverString.startsWith("v") || semverString.startsWith("V")) {
|
|
1430
|
+
return semverString.slice(1);
|
|
1431
|
+
}
|
|
1432
|
+
if (semverString.includes("@")) {
|
|
1433
|
+
return semverString.split("@").at(-1) ?? "";
|
|
1434
|
+
}
|
|
1435
|
+
return semverString;
|
|
1341
1436
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
function li(text, order = "unordered") {
|
|
1345
|
-
const style = order === "unordered" ? "-" : "- [ ]";
|
|
1346
|
-
return `${style} ${text}`;
|
|
1437
|
+
function isSemver(semverString = "") {
|
|
1438
|
+
return valid(normalizeSemver(semverString)) != null;
|
|
1347
1439
|
}
|
|
1348
|
-
function
|
|
1349
|
-
return
|
|
1440
|
+
function sortSemvers(semverStrings) {
|
|
1441
|
+
return semverStrings.map(normalizeSemver).filter(isSemver).sort(rcompare);
|
|
1350
1442
|
}
|
|
1351
1443
|
|
|
1352
|
-
// packages/utils/src/lib/
|
|
1353
|
-
function
|
|
1354
|
-
|
|
1444
|
+
// packages/utils/src/lib/git/git.commits-and-tags.ts
|
|
1445
|
+
async function getLatestCommit(git = simpleGit2()) {
|
|
1446
|
+
const log2 = await git.log({
|
|
1447
|
+
maxCount: 1,
|
|
1448
|
+
// git log -1 --pretty=format:"%H %s %an %aI" - See: https://git-scm.com/docs/pretty-formats
|
|
1449
|
+
format: { hash: "%H", message: "%s", author: "%an", date: "%aI" }
|
|
1450
|
+
});
|
|
1451
|
+
return commitSchema.parse(log2.latest);
|
|
1355
1452
|
}
|
|
1356
|
-
|
|
1357
|
-
//
|
|
1358
|
-
|
|
1359
|
-
|
|
1453
|
+
async function getCurrentBranchOrTag(git = simpleGit2()) {
|
|
1454
|
+
return await git.branch().then((r) => r.current) || // If no current branch, try to get the tag
|
|
1455
|
+
// @TODO use simple git
|
|
1456
|
+
await git.raw(["describe", "--tags", "--exact-match"]).then((out) => out.trim());
|
|
1360
1457
|
}
|
|
1361
|
-
function
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1458
|
+
function validateFilter({ from, to }) {
|
|
1459
|
+
if (to && !from) {
|
|
1460
|
+
throw new Error(
|
|
1461
|
+
`filter needs the "from" option defined to accept the "to" option.
|
|
1462
|
+
`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1366
1465
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
["left", ":--"],
|
|
1371
|
-
["center", ":--:"],
|
|
1372
|
-
["right", "--:"]
|
|
1373
|
-
]);
|
|
1374
|
-
function tableRow(rows) {
|
|
1375
|
-
return `|${rows.join("|")}|`;
|
|
1376
|
-
}
|
|
1377
|
-
function table2(data) {
|
|
1378
|
-
if (data.rows.length === 0) {
|
|
1379
|
-
throw new Error("Data can't be empty");
|
|
1466
|
+
function filterLogs(allTags, opt) {
|
|
1467
|
+
if (!opt) {
|
|
1468
|
+
return allTags;
|
|
1380
1469
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
)
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
);
|
|
1470
|
+
validateFilter(opt);
|
|
1471
|
+
const { from, to, maxCount } = opt;
|
|
1472
|
+
const finIndex = (tagName, fallback) => {
|
|
1473
|
+
const idx = allTags.indexOf(tagName ?? "");
|
|
1474
|
+
if (idx > -1) {
|
|
1475
|
+
return idx;
|
|
1476
|
+
}
|
|
1477
|
+
return fallback;
|
|
1478
|
+
};
|
|
1479
|
+
const fromIndex = finIndex(from, 0);
|
|
1480
|
+
const toIndex = finIndex(to, void 0);
|
|
1481
|
+
return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
|
|
1391
1482
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
link: link3,
|
|
1400
|
-
image,
|
|
1401
|
-
headline,
|
|
1402
|
-
h,
|
|
1403
|
-
h1,
|
|
1404
|
-
h2,
|
|
1405
|
-
h3,
|
|
1406
|
-
h4,
|
|
1407
|
-
h5,
|
|
1408
|
-
h6,
|
|
1409
|
-
indentation,
|
|
1410
|
-
lines,
|
|
1411
|
-
li,
|
|
1412
|
-
section,
|
|
1413
|
-
paragraphs,
|
|
1414
|
-
table: table2
|
|
1415
|
-
};
|
|
1416
|
-
var html = {
|
|
1417
|
-
bold,
|
|
1418
|
-
italic,
|
|
1419
|
-
code,
|
|
1420
|
-
link: link2,
|
|
1421
|
-
details,
|
|
1422
|
-
table
|
|
1423
|
-
};
|
|
1424
|
-
|
|
1425
|
-
// packages/utils/src/lib/reports/utils.ts
|
|
1426
|
-
var { image: image2, bold: boldMd } = md;
|
|
1427
|
-
function formatReportScore(score) {
|
|
1428
|
-
const scaledScore = score * 100;
|
|
1429
|
-
const roundedScore = Math.round(scaledScore);
|
|
1430
|
-
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
1431
|
-
}
|
|
1432
|
-
function formatScoreWithColor(score, options) {
|
|
1433
|
-
const styledNumber = options?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
|
|
1434
|
-
return `${scoreMarker(score)} ${styledNumber}`;
|
|
1483
|
+
async function getHashFromTag(tag, git = simpleGit2()) {
|
|
1484
|
+
const tagDetails = await git.show(["--no-patch", "--format=%H", tag]);
|
|
1485
|
+
const hash = tagDetails.trim();
|
|
1486
|
+
return {
|
|
1487
|
+
hash: hash.split("\n").at(-1) ?? "",
|
|
1488
|
+
message: tag
|
|
1489
|
+
};
|
|
1435
1490
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
red: "\u{1F7E5}",
|
|
1444
|
-
yellow: "\u{1F7E8}",
|
|
1445
|
-
green: "\u{1F7E9}"
|
|
1446
|
-
}
|
|
1447
|
-
};
|
|
1448
|
-
function scoreMarker(score, markerType = "circle") {
|
|
1449
|
-
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
1450
|
-
return MARKERS[markerType].green;
|
|
1491
|
+
async function getSemverTags(opt = {}, git = simpleGit2()) {
|
|
1492
|
+
validateFilter(opt);
|
|
1493
|
+
const { targetBranch, ...options } = opt;
|
|
1494
|
+
let currentBranch;
|
|
1495
|
+
if (targetBranch) {
|
|
1496
|
+
currentBranch = await getCurrentBranchOrTag(git);
|
|
1497
|
+
await git.checkout(targetBranch);
|
|
1451
1498
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1499
|
+
const tagsRaw = await git.tag([
|
|
1500
|
+
"--merged",
|
|
1501
|
+
targetBranch ?? await getCurrentBranchOrTag(git)
|
|
1502
|
+
]);
|
|
1503
|
+
const allTags = tagsRaw.split(/\n/).map((tag) => tag.trim()).filter(Boolean).filter(isSemver);
|
|
1504
|
+
const relevantTags = filterLogs(allTags, options);
|
|
1505
|
+
const tagsWithHashes = await Promise.all(
|
|
1506
|
+
relevantTags.map((tag) => getHashFromTag(tag, git))
|
|
1507
|
+
);
|
|
1508
|
+
if (currentBranch) {
|
|
1509
|
+
await git.checkout(currentBranch);
|
|
1454
1510
|
}
|
|
1455
|
-
return
|
|
1511
|
+
return tagsWithHashes;
|
|
1456
1512
|
}
|
|
1457
|
-
function
|
|
1458
|
-
|
|
1459
|
-
|
|
1513
|
+
async function getHashes(options = {}, git = simpleGit2()) {
|
|
1514
|
+
const { targetBranch, from, to, maxCount, ...opt } = options;
|
|
1515
|
+
validateFilter({ from, to });
|
|
1516
|
+
let currentBranch;
|
|
1517
|
+
if (targetBranch) {
|
|
1518
|
+
currentBranch = await getCurrentBranchOrTag(git);
|
|
1519
|
+
await git.checkout(targetBranch);
|
|
1460
1520
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1521
|
+
const logs = await git.log({
|
|
1522
|
+
...opt,
|
|
1523
|
+
format: {
|
|
1524
|
+
hash: "%H",
|
|
1525
|
+
message: "%s"
|
|
1526
|
+
},
|
|
1527
|
+
from,
|
|
1528
|
+
to,
|
|
1529
|
+
maxCount
|
|
1530
|
+
});
|
|
1531
|
+
if (targetBranch) {
|
|
1532
|
+
await git.checkout(currentBranch);
|
|
1463
1533
|
}
|
|
1464
|
-
return
|
|
1465
|
-
}
|
|
1466
|
-
function colorByScoreDiff(text, diff) {
|
|
1467
|
-
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
1468
|
-
return shieldsBadge(text, color);
|
|
1534
|
+
return [...logs.all];
|
|
1469
1535
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1536
|
+
|
|
1537
|
+
// packages/utils/src/lib/group-by-status.ts
|
|
1538
|
+
function groupByStatus(results) {
|
|
1539
|
+
return results.reduce(
|
|
1540
|
+
(acc, result) => result.status === "fulfilled" ? { ...acc, fulfilled: [...acc.fulfilled, result] } : { ...acc, rejected: [...acc.rejected, result] },
|
|
1541
|
+
{ fulfilled: [], rejected: [] }
|
|
1474
1542
|
);
|
|
1475
1543
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
return
|
|
1544
|
+
|
|
1545
|
+
// packages/utils/src/lib/merge-configs.ts
|
|
1546
|
+
function mergeConfigs(config, ...configs) {
|
|
1547
|
+
return configs.reduce(
|
|
1548
|
+
(acc, obj) => ({
|
|
1549
|
+
...acc,
|
|
1550
|
+
...mergeCategories(acc.categories, obj.categories),
|
|
1551
|
+
...mergePlugins(acc.plugins, obj.plugins),
|
|
1552
|
+
...mergePersist(acc.persist, obj.persist),
|
|
1553
|
+
...mergeUpload(acc.upload, obj.upload)
|
|
1554
|
+
}),
|
|
1555
|
+
config
|
|
1556
|
+
);
|
|
1480
1557
|
}
|
|
1481
|
-
function
|
|
1482
|
-
if (
|
|
1483
|
-
return
|
|
1484
|
-
}
|
|
1485
|
-
if (severity === "warning") {
|
|
1486
|
-
return "\u26A0\uFE0F";
|
|
1558
|
+
function mergeCategories(a, b) {
|
|
1559
|
+
if (!a && !b) {
|
|
1560
|
+
return {};
|
|
1487
1561
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1562
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
1563
|
+
const addToMap = (categories) => {
|
|
1564
|
+
categories.forEach((newObject) => {
|
|
1565
|
+
if (mergedMap.has(newObject.slug)) {
|
|
1566
|
+
const existingObject = mergedMap.get(
|
|
1567
|
+
newObject.slug
|
|
1568
|
+
);
|
|
1569
|
+
mergedMap.set(newObject.slug, {
|
|
1570
|
+
...existingObject,
|
|
1571
|
+
...newObject,
|
|
1572
|
+
refs: mergeByUniqueCategoryRefCombination(
|
|
1573
|
+
existingObject?.refs,
|
|
1574
|
+
newObject.refs
|
|
1575
|
+
)
|
|
1576
|
+
});
|
|
1577
|
+
} else {
|
|
1578
|
+
mergedMap.set(newObject.slug, newObject);
|
|
1498
1579
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
)
|
|
1504
|
-
};
|
|
1505
|
-
},
|
|
1506
|
-
{}
|
|
1507
|
-
);
|
|
1508
|
-
return refs.reduce((acc, ref) => {
|
|
1509
|
-
if (ref.type === "group") {
|
|
1510
|
-
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
1511
|
-
return acc + (groupRefs?.length ?? 0);
|
|
1512
|
-
}
|
|
1513
|
-
return acc + 1;
|
|
1514
|
-
}, 0);
|
|
1515
|
-
}
|
|
1516
|
-
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1517
|
-
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1518
|
-
if (!auditPlugin) {
|
|
1519
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1580
|
+
});
|
|
1581
|
+
};
|
|
1582
|
+
if (a) {
|
|
1583
|
+
addToMap(a);
|
|
1520
1584
|
}
|
|
1521
|
-
|
|
1522
|
-
(
|
|
1523
|
-
);
|
|
1524
|
-
if (!audit) {
|
|
1525
|
-
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1585
|
+
if (b) {
|
|
1586
|
+
addToMap(b);
|
|
1526
1587
|
}
|
|
1527
|
-
return {
|
|
1528
|
-
...audit,
|
|
1529
|
-
weight,
|
|
1530
|
-
plugin
|
|
1531
|
-
};
|
|
1588
|
+
return { categories: [...mergedMap.values()] };
|
|
1532
1589
|
}
|
|
1533
|
-
function
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1537
|
-
}
|
|
1538
|
-
const group = groupPlugin.groups?.find(
|
|
1539
|
-
({ slug: groupSlug }) => groupSlug === slug
|
|
1540
|
-
);
|
|
1541
|
-
if (!group) {
|
|
1542
|
-
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1590
|
+
function mergePlugins(a, b) {
|
|
1591
|
+
if (!a && !b) {
|
|
1592
|
+
return { plugins: [] };
|
|
1543
1593
|
}
|
|
1544
|
-
const
|
|
1545
|
-
const
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
});
|
|
1550
|
-
return {
|
|
1551
|
-
...group,
|
|
1552
|
-
refs: sortedAuditRefs,
|
|
1553
|
-
plugin,
|
|
1554
|
-
weight
|
|
1594
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
1595
|
+
const addToMap = (plugins) => {
|
|
1596
|
+
plugins.forEach((newObject) => {
|
|
1597
|
+
mergedMap.set(newObject.slug, newObject);
|
|
1598
|
+
});
|
|
1555
1599
|
};
|
|
1600
|
+
if (a) {
|
|
1601
|
+
addToMap(a);
|
|
1602
|
+
}
|
|
1603
|
+
if (b) {
|
|
1604
|
+
addToMap(b);
|
|
1605
|
+
}
|
|
1606
|
+
return { plugins: [...mergedMap.values()] };
|
|
1556
1607
|
}
|
|
1557
|
-
function
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
{
|
|
1561
|
-
plugin,
|
|
1562
|
-
slug: ref.slug,
|
|
1563
|
-
weight: ref.weight,
|
|
1564
|
-
type: "audit"
|
|
1565
|
-
},
|
|
1566
|
-
plugins
|
|
1567
|
-
)
|
|
1568
|
-
).sort(compareCategoryAuditsAndGroups);
|
|
1569
|
-
}
|
|
1570
|
-
function compareCategoryAuditsAndGroups(a, b) {
|
|
1571
|
-
if (a.weight !== b.weight) {
|
|
1572
|
-
return b.weight - a.weight;
|
|
1608
|
+
function mergePersist(a, b) {
|
|
1609
|
+
if (!a && !b) {
|
|
1610
|
+
return {};
|
|
1573
1611
|
}
|
|
1574
|
-
if (a
|
|
1575
|
-
return a
|
|
1612
|
+
if (a) {
|
|
1613
|
+
return b ? { persist: { ...a, ...b } } : {};
|
|
1614
|
+
} else {
|
|
1615
|
+
return { persist: b };
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
function mergeByUniqueCategoryRefCombination(a, b) {
|
|
1619
|
+
const map = /* @__PURE__ */ new Map();
|
|
1620
|
+
const addToMap = (refs) => {
|
|
1621
|
+
refs.forEach((ref) => {
|
|
1622
|
+
const uniqueIdentification = `${ref.type}:${ref.plugin}:${ref.slug}`;
|
|
1623
|
+
if (map.has(uniqueIdentification)) {
|
|
1624
|
+
map.set(uniqueIdentification, {
|
|
1625
|
+
...map.get(uniqueIdentification),
|
|
1626
|
+
...ref
|
|
1627
|
+
});
|
|
1628
|
+
} else {
|
|
1629
|
+
map.set(uniqueIdentification, ref);
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
};
|
|
1633
|
+
if (a) {
|
|
1634
|
+
addToMap(a);
|
|
1576
1635
|
}
|
|
1577
|
-
if (
|
|
1578
|
-
|
|
1636
|
+
if (b) {
|
|
1637
|
+
addToMap(b);
|
|
1579
1638
|
}
|
|
1580
|
-
return
|
|
1639
|
+
return [...map.values()];
|
|
1581
1640
|
}
|
|
1582
|
-
function
|
|
1583
|
-
if (a
|
|
1584
|
-
return
|
|
1641
|
+
function mergeUpload(a, b) {
|
|
1642
|
+
if (!a && !b) {
|
|
1643
|
+
return {};
|
|
1585
1644
|
}
|
|
1586
|
-
if (a
|
|
1587
|
-
return b
|
|
1645
|
+
if (a) {
|
|
1646
|
+
return b ? { upload: { ...a, ...b } } : {};
|
|
1647
|
+
} else {
|
|
1648
|
+
return { upload: b };
|
|
1588
1649
|
}
|
|
1589
|
-
return a.title.localeCompare(b.title);
|
|
1590
|
-
}
|
|
1591
|
-
function compareIssueSeverity(severity1, severity2) {
|
|
1592
|
-
const levels = {
|
|
1593
|
-
info: 0,
|
|
1594
|
-
warning: 1,
|
|
1595
|
-
error: 2
|
|
1596
|
-
};
|
|
1597
|
-
return levels[severity1] - levels[severity2];
|
|
1598
1650
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1651
|
+
|
|
1652
|
+
// packages/utils/src/lib/progress.ts
|
|
1653
|
+
import { black, bold as bold2, gray as gray2, green } from "ansis";
|
|
1654
|
+
import { MultiProgressBars } from "multi-progress-bars";
|
|
1655
|
+
var barStyles = {
|
|
1656
|
+
active: (s) => green(s),
|
|
1657
|
+
done: (s) => gray2(s),
|
|
1658
|
+
idle: (s) => gray2(s)
|
|
1659
|
+
};
|
|
1660
|
+
var messageStyles = {
|
|
1661
|
+
active: (s) => black(s),
|
|
1662
|
+
done: (s) => bold2.green(s),
|
|
1663
|
+
idle: (s) => gray2(s)
|
|
1664
|
+
};
|
|
1665
|
+
var mpb;
|
|
1666
|
+
function getSingletonProgressBars(options) {
|
|
1667
|
+
if (!mpb) {
|
|
1668
|
+
mpb = new MultiProgressBars({
|
|
1669
|
+
progressWidth: TERMINAL_WIDTH,
|
|
1670
|
+
initMessage: "",
|
|
1671
|
+
border: true,
|
|
1672
|
+
...options
|
|
1673
|
+
});
|
|
1606
1674
|
}
|
|
1607
|
-
|
|
1608
|
-
return text;
|
|
1609
|
-
}
|
|
1610
|
-
function throwIsNotPresentError(itemName, presentPlace) {
|
|
1611
|
-
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
1612
|
-
}
|
|
1613
|
-
function getPluginNameFromSlug(slug, plugins) {
|
|
1614
|
-
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
1675
|
+
return mpb;
|
|
1615
1676
|
}
|
|
1616
|
-
function
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1677
|
+
function getProgressBar(taskName) {
|
|
1678
|
+
const tasks = getSingletonProgressBars();
|
|
1679
|
+
tasks.addTask(taskName, {
|
|
1680
|
+
type: "percentage",
|
|
1681
|
+
percentage: 0,
|
|
1682
|
+
message: "",
|
|
1683
|
+
barTransformFn: barStyles.idle
|
|
1684
|
+
});
|
|
1685
|
+
return {
|
|
1686
|
+
incrementInSteps: (numPlugins) => {
|
|
1687
|
+
tasks.incrementTask(taskName, {
|
|
1688
|
+
percentage: 1 / numPlugins,
|
|
1689
|
+
barTransformFn: barStyles.active
|
|
1690
|
+
});
|
|
1691
|
+
},
|
|
1692
|
+
updateTitle: (title) => {
|
|
1693
|
+
tasks.updateTask(taskName, {
|
|
1694
|
+
message: title,
|
|
1695
|
+
barTransformFn: barStyles.active
|
|
1696
|
+
});
|
|
1697
|
+
},
|
|
1698
|
+
endProgress: (message = "") => {
|
|
1699
|
+
tasks.done(taskName, {
|
|
1700
|
+
message: messageStyles.done(message),
|
|
1701
|
+
barTransformFn: barStyles.done
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1639
1705
|
}
|
|
1640
1706
|
|
|
1641
|
-
// packages/utils/src/lib/
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
}
|
|
1652
|
-
};
|
|
1653
|
-
function executeProcess(cfg) {
|
|
1654
|
-
const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
|
|
1655
|
-
const { onStdout, onError, onComplete } = observer ?? {};
|
|
1656
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
1657
|
-
const start = performance.now();
|
|
1658
|
-
return new Promise((resolve, reject) => {
|
|
1659
|
-
const process2 = spawn(command, args, { cwd, shell: true });
|
|
1660
|
-
let stdout = "";
|
|
1661
|
-
let stderr = "";
|
|
1662
|
-
process2.stdout.on("data", (data) => {
|
|
1663
|
-
stdout += String(data);
|
|
1664
|
-
onStdout?.(String(data));
|
|
1665
|
-
});
|
|
1666
|
-
process2.stderr.on("data", (data) => {
|
|
1667
|
-
stderr += String(data);
|
|
1668
|
-
});
|
|
1669
|
-
process2.on("error", (err) => {
|
|
1670
|
-
stderr += err.toString();
|
|
1671
|
-
});
|
|
1672
|
-
process2.on("close", (code3) => {
|
|
1673
|
-
const timings = { date, duration: calcDuration(start) };
|
|
1674
|
-
if (code3 === 0 || ignoreExitCode) {
|
|
1675
|
-
onComplete?.();
|
|
1676
|
-
resolve({ code: code3, stdout, stderr, ...timings });
|
|
1677
|
-
} else {
|
|
1678
|
-
const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
|
|
1679
|
-
onError?.(errorMsg);
|
|
1680
|
-
reject(errorMsg);
|
|
1681
|
-
}
|
|
1682
|
-
});
|
|
1683
|
-
});
|
|
1707
|
+
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1708
|
+
function listGroupsFromAllPlugins(report) {
|
|
1709
|
+
return report.plugins.flatMap(
|
|
1710
|
+
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
function listAuditsFromAllPlugins(report) {
|
|
1714
|
+
return report.plugins.flatMap(
|
|
1715
|
+
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1716
|
+
);
|
|
1684
1717
|
}
|
|
1685
1718
|
|
|
1686
|
-
// packages/utils/src/lib/
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1719
|
+
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1720
|
+
import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
|
|
1721
|
+
|
|
1722
|
+
// packages/utils/src/lib/text-formats/constants.ts
|
|
1723
|
+
var NEW_LINE = "\n";
|
|
1724
|
+
var TAB = " ";
|
|
1725
|
+
var SPACE = " ";
|
|
1726
|
+
var HIERARCHY = {
|
|
1727
|
+
level_1: 1,
|
|
1728
|
+
level_2: 2,
|
|
1729
|
+
level_3: 3,
|
|
1730
|
+
level_4: 4,
|
|
1731
|
+
level_5: 5,
|
|
1732
|
+
level_6: 6
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
// packages/utils/src/lib/text-formats/html/details.ts
|
|
1736
|
+
function details(title, content, cfg = { open: false }) {
|
|
1737
|
+
return `<details${cfg.open ? " open" : ""}>${NEW_LINE}<summary>${title}</summary>${NEW_LINE}${// ⚠️ The blank line is needed to ensure Markdown in content is rendered correctly.
|
|
1738
|
+
NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
|
|
1739
|
+
// ⚠️ The blank line ensure Markdown in content is rendered correctly.
|
|
1740
|
+
NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
|
|
1741
|
+
NEW_LINE}`;
|
|
1692
1742
|
}
|
|
1693
1743
|
|
|
1694
|
-
// packages/utils/src/lib/
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
return git.revparse("--show-toplevel");
|
|
1744
|
+
// packages/utils/src/lib/text-formats/html/font-style.ts
|
|
1745
|
+
var boldElement = "b";
|
|
1746
|
+
function bold3(text) {
|
|
1747
|
+
return `<${boldElement}>${text}</${boldElement}>`;
|
|
1699
1748
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
return toUnixPath(relativePath);
|
|
1749
|
+
var italicElement = "i";
|
|
1750
|
+
function italic(text) {
|
|
1751
|
+
return `<${italicElement}>${text}</${italicElement}>`;
|
|
1704
1752
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
return
|
|
1753
|
+
var codeElement = "code";
|
|
1754
|
+
function code(text) {
|
|
1755
|
+
return `<${codeElement}>${text}</${codeElement}>`;
|
|
1708
1756
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
return false;
|
|
1721
|
-
}
|
|
1722
|
-
if (typeof value === "number" && value === 0) {
|
|
1723
|
-
return false;
|
|
1724
|
-
}
|
|
1725
|
-
return !(typeof value === "boolean" && !value);
|
|
1726
|
-
}
|
|
1727
|
-
)
|
|
1757
|
+
|
|
1758
|
+
// packages/utils/src/lib/text-formats/html/link.ts
|
|
1759
|
+
function link2(href, text) {
|
|
1760
|
+
return `<a href="${href}">${text || href}</a>`;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// packages/utils/src/lib/text-formats/table.ts
|
|
1764
|
+
function rowToStringArray({ rows, columns = [] }) {
|
|
1765
|
+
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
1766
|
+
throw new TypeError(
|
|
1767
|
+
"Column can`t be object when rows are primitive values"
|
|
1728
1768
|
);
|
|
1729
1769
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1770
|
+
return rows.map((row) => {
|
|
1771
|
+
if (Array.isArray(row)) {
|
|
1772
|
+
return row.map(String);
|
|
1773
|
+
}
|
|
1774
|
+
const objectRow = row;
|
|
1775
|
+
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1776
|
+
return Object.values(objectRow).map(
|
|
1777
|
+
(value) => value == null ? "" : String(value)
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
return columns.map(
|
|
1781
|
+
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1738
1782
|
);
|
|
1739
|
-
}
|
|
1740
|
-
};
|
|
1741
|
-
async function guardAgainstLocalChanges(git = simpleGit()) {
|
|
1742
|
-
const status = await git.status(["-s"]);
|
|
1743
|
-
if (status.files.length > 0) {
|
|
1744
|
-
throw new GitStatusError(status);
|
|
1745
|
-
}
|
|
1783
|
+
});
|
|
1746
1784
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1785
|
+
function columnsToStringArray({
|
|
1786
|
+
rows,
|
|
1787
|
+
columns = []
|
|
1788
|
+
}) {
|
|
1789
|
+
const firstRow = rows.at(0);
|
|
1790
|
+
const primitiveRows = Array.isArray(firstRow);
|
|
1791
|
+
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1792
|
+
throw new Error("invalid union type. Caught by model parsing.");
|
|
1752
1793
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1759
|
-
|
|
1760
|
-
// packages/utils/src/lib/semver.ts
|
|
1761
|
-
import { rcompare, valid } from "semver";
|
|
1762
|
-
function normalizeSemver(semverString) {
|
|
1763
|
-
if (semverString.startsWith("v") || semverString.startsWith("V")) {
|
|
1764
|
-
return semverString.slice(1);
|
|
1794
|
+
if (columns.length === 0) {
|
|
1795
|
+
if (Array.isArray(firstRow)) {
|
|
1796
|
+
return firstRow.map((_, idx) => String(idx));
|
|
1797
|
+
}
|
|
1798
|
+
return Object.keys(firstRow);
|
|
1765
1799
|
}
|
|
1766
|
-
if (
|
|
1767
|
-
return
|
|
1800
|
+
if (typeof columns.at(0) === "string") {
|
|
1801
|
+
return columns.map(String);
|
|
1768
1802
|
}
|
|
1769
|
-
|
|
1770
|
-
}
|
|
1771
|
-
function isSemver(semverString = "") {
|
|
1772
|
-
return valid(normalizeSemver(semverString)) != null;
|
|
1773
|
-
}
|
|
1774
|
-
function sortSemvers(semverStrings) {
|
|
1775
|
-
return semverStrings.map(normalizeSemver).filter(isSemver).sort(rcompare);
|
|
1803
|
+
const cols = columns;
|
|
1804
|
+
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1776
1805
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1806
|
+
function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
|
|
1807
|
+
const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
|
|
1808
|
+
if (typeof column === "string") {
|
|
1809
|
+
return column;
|
|
1810
|
+
} else if (typeof column === "object") {
|
|
1811
|
+
return column.align ?? "center";
|
|
1812
|
+
} else {
|
|
1813
|
+
return "center";
|
|
1814
|
+
}
|
|
1786
1815
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1816
|
+
function getColumnAlignmentForIndex(targetIdx, columns = []) {
|
|
1817
|
+
const column = columns.at(targetIdx);
|
|
1818
|
+
if (column == null) {
|
|
1819
|
+
return "center";
|
|
1820
|
+
} else if (typeof column === "string") {
|
|
1821
|
+
return column;
|
|
1822
|
+
} else if (typeof column === "object") {
|
|
1823
|
+
return column.align ?? "center";
|
|
1824
|
+
} else {
|
|
1825
|
+
return "center";
|
|
1826
|
+
}
|
|
1791
1827
|
}
|
|
1792
|
-
function
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1828
|
+
function getColumnAlignments(tableData) {
|
|
1829
|
+
const { rows, columns = [] } = tableData;
|
|
1830
|
+
if (rows.at(0) == null) {
|
|
1831
|
+
throw new Error("first row can`t be undefined.");
|
|
1832
|
+
}
|
|
1833
|
+
if (Array.isArray(rows.at(0))) {
|
|
1834
|
+
const firstPrimitiveRow = rows.at(0);
|
|
1835
|
+
return Array.from({ length: firstPrimitiveRow.length }).map(
|
|
1836
|
+
(_, idx) => getColumnAlignmentForIndex(idx, columns)
|
|
1797
1837
|
);
|
|
1798
1838
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1839
|
+
const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
|
|
1840
|
+
if (columns.length > 0) {
|
|
1841
|
+
return columns.map(
|
|
1842
|
+
(column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
|
|
1843
|
+
column.key,
|
|
1844
|
+
idx,
|
|
1845
|
+
columns
|
|
1846
|
+
)
|
|
1847
|
+
);
|
|
1803
1848
|
}
|
|
1804
|
-
|
|
1805
|
-
const { from, to, maxCount } = opt;
|
|
1806
|
-
const finIndex = (tagName, fallback) => {
|
|
1807
|
-
const idx = allTags.indexOf(tagName ?? "");
|
|
1808
|
-
if (idx > -1) {
|
|
1809
|
-
return idx;
|
|
1810
|
-
}
|
|
1811
|
-
return fallback;
|
|
1812
|
-
};
|
|
1813
|
-
const fromIndex = finIndex(from, 0);
|
|
1814
|
-
const toIndex = finIndex(to, void 0);
|
|
1815
|
-
return allTags.slice(fromIndex, toIndex ? toIndex + 1 : toIndex).slice(0, maxCount ?? void 0);
|
|
1849
|
+
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1816
1850
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
return {
|
|
1821
|
-
hash: hash.split("\n").at(-1) ?? "",
|
|
1822
|
-
message: tag
|
|
1823
|
-
};
|
|
1851
|
+
|
|
1852
|
+
// packages/utils/src/lib/text-formats/html/table.ts
|
|
1853
|
+
function wrap(elem, content) {
|
|
1854
|
+
return `<${elem}>${content}</${elem}>${NEW_LINE}`;
|
|
1824
1855
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1856
|
+
function wrapRow(content) {
|
|
1857
|
+
const elem = "tr";
|
|
1858
|
+
return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
|
|
1859
|
+
}
|
|
1860
|
+
function table(tableData) {
|
|
1861
|
+
if (tableData.rows.length === 0) {
|
|
1862
|
+
throw new Error("Data can't be empty");
|
|
1832
1863
|
}
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1864
|
+
const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
|
|
1865
|
+
const tableHeaderRow = wrapRow(tableHeaderCols);
|
|
1866
|
+
const tableBody = rowToStringArray(tableData).map((arr) => {
|
|
1867
|
+
const columns = arr.map((s) => wrap("td", s)).join("");
|
|
1868
|
+
return wrapRow(columns);
|
|
1869
|
+
}).join("");
|
|
1870
|
+
return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// packages/utils/src/lib/text-formats/index.ts
|
|
1874
|
+
var html = {
|
|
1875
|
+
bold: bold3,
|
|
1876
|
+
italic,
|
|
1877
|
+
code,
|
|
1878
|
+
link: link2,
|
|
1879
|
+
details,
|
|
1880
|
+
table
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
// packages/utils/src/lib/reports/formatting.ts
|
|
1884
|
+
import { MarkdownDocument, md as md2 } from "build-md";
|
|
1885
|
+
function tableSection(tableData, options) {
|
|
1886
|
+
if (tableData.rows.length === 0) {
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
const { level = HIERARCHY.level_4 } = options ?? {};
|
|
1890
|
+
const columns = columnsToStringArray(tableData);
|
|
1891
|
+
const alignments = getColumnAlignments(tableData);
|
|
1892
|
+
const rows = rowToStringArray(tableData);
|
|
1893
|
+
return new MarkdownDocument().heading(level, tableData.title).table(
|
|
1894
|
+
columns.map((heading, i) => {
|
|
1895
|
+
const alignment = alignments[i];
|
|
1896
|
+
if (alignment) {
|
|
1897
|
+
return { heading, alignment };
|
|
1898
|
+
}
|
|
1899
|
+
return heading;
|
|
1900
|
+
}),
|
|
1901
|
+
rows
|
|
1841
1902
|
);
|
|
1842
|
-
if (currentBranch) {
|
|
1843
|
-
await git.checkout(currentBranch);
|
|
1844
|
-
}
|
|
1845
|
-
return tagsWithHashes;
|
|
1846
1903
|
}
|
|
1847
|
-
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1904
|
+
function metaDescription(audit) {
|
|
1905
|
+
const docsUrl = audit.docsUrl;
|
|
1906
|
+
const description = audit.description?.trim();
|
|
1907
|
+
if (docsUrl) {
|
|
1908
|
+
const docsLink = md2.link(docsUrl, "\u{1F4D6} Docs");
|
|
1909
|
+
if (!description) {
|
|
1910
|
+
return docsLink;
|
|
1911
|
+
}
|
|
1912
|
+
const parsedDescription = description.endsWith("```") ? `${description}
|
|
1913
|
+
|
|
1914
|
+
` : `${description} `;
|
|
1915
|
+
return md2`${parsedDescription}${docsLink}`;
|
|
1854
1916
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
format: {
|
|
1858
|
-
hash: "%H",
|
|
1859
|
-
message: "%s"
|
|
1860
|
-
},
|
|
1861
|
-
from,
|
|
1862
|
-
to,
|
|
1863
|
-
maxCount
|
|
1864
|
-
});
|
|
1865
|
-
if (targetBranch) {
|
|
1866
|
-
await git.checkout(currentBranch);
|
|
1917
|
+
if (description && description.trim().length > 0) {
|
|
1918
|
+
return description;
|
|
1867
1919
|
}
|
|
1868
|
-
return
|
|
1920
|
+
return "";
|
|
1869
1921
|
}
|
|
1870
1922
|
|
|
1871
|
-
// packages/utils/src/lib/
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1923
|
+
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1924
|
+
import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
|
|
1925
|
+
|
|
1926
|
+
// packages/utils/src/lib/reports/sorting.ts
|
|
1927
|
+
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1928
|
+
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1929
|
+
if (!auditPlugin) {
|
|
1930
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1931
|
+
}
|
|
1932
|
+
const audit = auditPlugin.audits.find(
|
|
1933
|
+
({ slug: auditSlug }) => auditSlug === slug
|
|
1876
1934
|
);
|
|
1935
|
+
if (!audit) {
|
|
1936
|
+
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1937
|
+
}
|
|
1938
|
+
return {
|
|
1939
|
+
...audit,
|
|
1940
|
+
weight,
|
|
1941
|
+
plugin
|
|
1942
|
+
};
|
|
1877
1943
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
mpb = new MultiProgressBars({
|
|
1896
|
-
progressWidth: TERMINAL_WIDTH,
|
|
1897
|
-
initMessage: "",
|
|
1898
|
-
border: true,
|
|
1899
|
-
...options
|
|
1900
|
-
});
|
|
1944
|
+
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1945
|
+
return group.refs.map(
|
|
1946
|
+
(ref) => getSortableAuditByRef(
|
|
1947
|
+
{
|
|
1948
|
+
plugin,
|
|
1949
|
+
slug: ref.slug,
|
|
1950
|
+
weight: ref.weight,
|
|
1951
|
+
type: "audit"
|
|
1952
|
+
},
|
|
1953
|
+
plugins
|
|
1954
|
+
)
|
|
1955
|
+
).sort(compareCategoryAuditsAndGroups);
|
|
1956
|
+
}
|
|
1957
|
+
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1958
|
+
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1959
|
+
if (!groupPlugin) {
|
|
1960
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1901
1961
|
}
|
|
1902
|
-
|
|
1962
|
+
const group = groupPlugin.groups?.find(
|
|
1963
|
+
({ slug: groupSlug }) => groupSlug === slug
|
|
1964
|
+
);
|
|
1965
|
+
if (!group) {
|
|
1966
|
+
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1967
|
+
}
|
|
1968
|
+
const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
|
|
1969
|
+
const sortedAuditRefs = [...group.refs].sort((a, b) => {
|
|
1970
|
+
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1971
|
+
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1972
|
+
return aIndex - bIndex;
|
|
1973
|
+
});
|
|
1974
|
+
return {
|
|
1975
|
+
...group,
|
|
1976
|
+
refs: sortedAuditRefs,
|
|
1977
|
+
plugin,
|
|
1978
|
+
weight
|
|
1979
|
+
};
|
|
1903
1980
|
}
|
|
1904
|
-
function
|
|
1905
|
-
const
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1981
|
+
function sortReport(report) {
|
|
1982
|
+
const { categories, plugins } = report;
|
|
1983
|
+
const sortedCategories = categories.map((category) => {
|
|
1984
|
+
const { audits, groups } = category.refs.reduce(
|
|
1985
|
+
(acc, ref) => ({
|
|
1986
|
+
...acc,
|
|
1987
|
+
...ref.type === "group" ? {
|
|
1988
|
+
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
1989
|
+
} : {
|
|
1990
|
+
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
1991
|
+
}
|
|
1992
|
+
}),
|
|
1993
|
+
{ groups: [], audits: [] }
|
|
1994
|
+
);
|
|
1995
|
+
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
1996
|
+
compareCategoryAuditsAndGroups
|
|
1997
|
+
);
|
|
1998
|
+
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
1999
|
+
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
2000
|
+
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
2001
|
+
);
|
|
2002
|
+
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
2003
|
+
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
2004
|
+
);
|
|
2005
|
+
return aIndex - bIndex;
|
|
2006
|
+
});
|
|
2007
|
+
return { ...category, refs: sortedRefs };
|
|
1911
2008
|
});
|
|
1912
2009
|
return {
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
barTransformFn: barStyles.active
|
|
1917
|
-
});
|
|
1918
|
-
},
|
|
1919
|
-
updateTitle: (title) => {
|
|
1920
|
-
tasks.updateTask(taskName, {
|
|
1921
|
-
message: title,
|
|
1922
|
-
barTransformFn: barStyles.active
|
|
1923
|
-
});
|
|
1924
|
-
},
|
|
1925
|
-
endProgress: (message = "") => {
|
|
1926
|
-
tasks.done(taskName, {
|
|
1927
|
-
message: messageStyles.done(message),
|
|
1928
|
-
barTransformFn: barStyles.done
|
|
1929
|
-
});
|
|
1930
|
-
}
|
|
2010
|
+
...report,
|
|
2011
|
+
categories: sortedCategories,
|
|
2012
|
+
plugins: sortPlugins(plugins)
|
|
1931
2013
|
};
|
|
1932
2014
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
// packages/utils/src/lib/reports/formatting.ts
|
|
1947
|
-
var { headline: headline2, lines: lines2, link: link4, section: section2, table: table3 } = md;
|
|
1948
|
-
function tableSection(tableData, options) {
|
|
1949
|
-
if (tableData.rows.length === 0) {
|
|
1950
|
-
return "";
|
|
1951
|
-
}
|
|
1952
|
-
const { level = 4 } = options ?? {};
|
|
1953
|
-
const render = (h7, l) => l === 0 ? h7 : headline2(h7, l);
|
|
1954
|
-
return lines2(
|
|
1955
|
-
tableData.title && render(tableData.title, level),
|
|
1956
|
-
table3(tableData)
|
|
1957
|
-
);
|
|
1958
|
-
}
|
|
1959
|
-
function metaDescription({
|
|
1960
|
-
docsUrl,
|
|
1961
|
-
description
|
|
1962
|
-
}) {
|
|
1963
|
-
if (docsUrl) {
|
|
1964
|
-
const docsLink = link4(docsUrl, "\u{1F4D6} Docs");
|
|
1965
|
-
if (!description) {
|
|
1966
|
-
return section2(docsLink);
|
|
1967
|
-
}
|
|
1968
|
-
const parsedDescription = description.toString().endsWith("```") ? `${description}${NEW_LINE + NEW_LINE}` : `${description}${SPACE}`;
|
|
1969
|
-
return section2(`${parsedDescription}${docsLink}`);
|
|
1970
|
-
}
|
|
1971
|
-
if (description && description.trim().length > 0) {
|
|
1972
|
-
return section2(description);
|
|
1973
|
-
}
|
|
1974
|
-
return "";
|
|
2015
|
+
function sortPlugins(plugins) {
|
|
2016
|
+
return plugins.map((plugin) => ({
|
|
2017
|
+
...plugin,
|
|
2018
|
+
audits: [...plugin.audits].sort(compareAudits).map(
|
|
2019
|
+
(audit) => audit.details?.issues ? {
|
|
2020
|
+
...audit,
|
|
2021
|
+
details: {
|
|
2022
|
+
...audit.details,
|
|
2023
|
+
issues: [...audit.details.issues].sort(compareIssues)
|
|
2024
|
+
}
|
|
2025
|
+
} : audit
|
|
2026
|
+
)
|
|
2027
|
+
}));
|
|
1975
2028
|
}
|
|
1976
2029
|
|
|
1977
2030
|
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1978
|
-
var { link: link5, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
|
|
1979
2031
|
function categoriesOverviewSection(report) {
|
|
1980
2032
|
const { categories, plugins } = report;
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2033
|
+
return new MarkdownDocument2().table(
|
|
2034
|
+
[
|
|
2035
|
+
{ heading: "\u{1F3F7} Category", alignment: "left" },
|
|
2036
|
+
{ heading: "\u2B50 Score", alignment: "center" },
|
|
2037
|
+
{ heading: "\u{1F6E1} Audits", alignment: "center" }
|
|
2038
|
+
],
|
|
2039
|
+
categories.map(({ title, refs, score, isBinary }) => [
|
|
2040
|
+
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
|
|
2041
|
+
// The heading "ID" is inferred from the heading text in Markdown.
|
|
2042
|
+
md3.link(`#${slugify(title)}`, title),
|
|
2043
|
+
md3`${scoreMarker(score)} ${md3.bold(
|
|
2044
|
+
formatReportScore(score)
|
|
2045
|
+
)}${binaryIconSuffix(score, isBinary)}`,
|
|
2046
|
+
countCategoryAudits(refs, plugins).toString()
|
|
2047
|
+
])
|
|
2048
|
+
);
|
|
1996
2049
|
}
|
|
1997
2050
|
function categoriesDetailsSection(report) {
|
|
1998
2051
|
const { categories, plugins } = report;
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
category.score
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
...categoryMDItems
|
|
2026
|
-
);
|
|
2027
|
-
});
|
|
2028
|
-
return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
|
|
2052
|
+
return new MarkdownDocument2().heading(HIERARCHY.level_2, "\u{1F3F7} Categories").$foreach(
|
|
2053
|
+
categories,
|
|
2054
|
+
(doc, category) => doc.heading(HIERARCHY.level_3, category.title).paragraph(metaDescription(category)).paragraph(
|
|
2055
|
+
md3`${scoreMarker(category.score)} Score: ${md3.bold(
|
|
2056
|
+
formatReportScore(category.score)
|
|
2057
|
+
)}${binaryIconSuffix(category.score, category.isBinary)}`
|
|
2058
|
+
).list(
|
|
2059
|
+
category.refs.map((ref) => {
|
|
2060
|
+
if (ref.type === "group") {
|
|
2061
|
+
const group = getSortableGroupByRef(ref, plugins);
|
|
2062
|
+
const groupAudits = group.refs.map(
|
|
2063
|
+
(groupRef) => getSortableAuditByRef(
|
|
2064
|
+
{ ...groupRef, plugin: group.plugin, type: "audit" },
|
|
2065
|
+
plugins
|
|
2066
|
+
)
|
|
2067
|
+
);
|
|
2068
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
2069
|
+
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
2070
|
+
} else {
|
|
2071
|
+
const audit = getSortableAuditByRef(ref, plugins);
|
|
2072
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
2073
|
+
return categoryRef(audit, pluginTitle);
|
|
2074
|
+
}
|
|
2075
|
+
})
|
|
2076
|
+
)
|
|
2077
|
+
);
|
|
2029
2078
|
}
|
|
2030
2079
|
function categoryRef({ title, score, value, displayValue }, pluginTitle) {
|
|
2031
|
-
const auditTitleAsLink =
|
|
2080
|
+
const auditTitleAsLink = md3.link(
|
|
2032
2081
|
`#${slugify(title)}-${slugify(pluginTitle)}`,
|
|
2033
2082
|
title
|
|
2034
2083
|
);
|
|
2035
2084
|
const marker = scoreMarker(score, "square");
|
|
2036
|
-
return
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
)}`
|
|
2040
|
-
);
|
|
2085
|
+
return md3`${marker} ${auditTitleAsLink} (${md3.italic(
|
|
2086
|
+
pluginTitle
|
|
2087
|
+
)}) - ${md3.bold((displayValue || value).toString())}`;
|
|
2041
2088
|
}
|
|
2042
2089
|
function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
|
|
2043
|
-
const groupTitle =
|
|
2044
|
-
|
|
2045
|
-
)
|
|
2046
|
-
const
|
|
2047
|
-
(
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
);
|
|
2060
|
-
}
|
|
2090
|
+
const groupTitle = md3`${scoreMarker(score)} ${title} (${md3.italic(
|
|
2091
|
+
pluginTitle
|
|
2092
|
+
)})`;
|
|
2093
|
+
const auditsList = md3.list(
|
|
2094
|
+
groupAudits.map(
|
|
2095
|
+
({ title: auditTitle, score: auditScore, value, displayValue }) => {
|
|
2096
|
+
const auditTitleLink = md3.link(
|
|
2097
|
+
`#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
|
|
2098
|
+
auditTitle
|
|
2099
|
+
);
|
|
2100
|
+
const marker = scoreMarker(auditScore, "square");
|
|
2101
|
+
return md3`${marker} ${auditTitleLink} - ${md3.bold(
|
|
2102
|
+
String(displayValue ?? value)
|
|
2103
|
+
)}`;
|
|
2104
|
+
}
|
|
2105
|
+
)
|
|
2061
2106
|
);
|
|
2062
|
-
return
|
|
2107
|
+
return md3`${groupTitle}${auditsList}`;
|
|
2108
|
+
}
|
|
2109
|
+
function binaryIconSuffix(score, isBinary) {
|
|
2110
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, { prefix: " " });
|
|
2063
2111
|
}
|
|
2064
2112
|
|
|
2065
2113
|
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
2066
|
-
var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link6, section: section4, code: codeMd } = md;
|
|
2067
|
-
var { bold: boldHtml, details: details2 } = html;
|
|
2068
2114
|
function auditDetailsAuditValue({
|
|
2069
2115
|
score,
|
|
2070
2116
|
value,
|
|
2071
2117
|
displayValue
|
|
2072
2118
|
}) {
|
|
2073
|
-
return `${scoreMarker(score, "square")} ${
|
|
2119
|
+
return md4`${scoreMarker(score, "square")} ${md4.bold(
|
|
2074
2120
|
String(displayValue ?? value)
|
|
2075
2121
|
)} (score: ${formatReportScore(score)})`;
|
|
2076
2122
|
}
|
|
2077
2123
|
function generateMdReport(report) {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
`${FOOTER_PREFIX}${SPACE}${link6(README_LINK, "Code PushUp")}`
|
|
2086
|
-
);
|
|
2124
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$if(
|
|
2125
|
+
report.categories.length > 0,
|
|
2126
|
+
(doc) => doc.$concat(
|
|
2127
|
+
categoriesOverviewSection(report),
|
|
2128
|
+
categoriesDetailsSection(report)
|
|
2129
|
+
)
|
|
2130
|
+
).$concat(auditsSection(report), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
|
|
2087
2131
|
}
|
|
2088
2132
|
function auditDetailsIssues(issues = []) {
|
|
2089
2133
|
if (issues.length === 0) {
|
|
2090
|
-
return
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
return { severity, message, file, line: "" };
|
|
2104
|
-
}
|
|
2105
|
-
const { startLine, endLine } = sourceVal.position;
|
|
2106
|
-
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
2107
|
-
return { severity, message, file, line };
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_4, "Issues").table(
|
|
2137
|
+
[
|
|
2138
|
+
{ heading: "Severity", alignment: "center" },
|
|
2139
|
+
{ heading: "Message", alignment: "left" },
|
|
2140
|
+
{ heading: "Source file", alignment: "left" },
|
|
2141
|
+
{ heading: "Line(s)", alignment: "center" }
|
|
2142
|
+
],
|
|
2143
|
+
issues.map(({ severity: level, message, source }) => {
|
|
2144
|
+
const severity = md4`${severityMarker(level)} ${md4.italic(level)}`;
|
|
2145
|
+
if (!source) {
|
|
2146
|
+
return [severity, message];
|
|
2108
2147
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2148
|
+
const file = md4.code(source.file);
|
|
2149
|
+
if (!source.position) {
|
|
2150
|
+
return [severity, message, file];
|
|
2151
|
+
}
|
|
2152
|
+
const { startLine, endLine } = source.position;
|
|
2153
|
+
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
2154
|
+
return [severity, message, file, line];
|
|
2155
|
+
})
|
|
2156
|
+
);
|
|
2112
2157
|
}
|
|
2113
2158
|
function auditDetails(audit) {
|
|
2114
|
-
const { table:
|
|
2159
|
+
const { table: table2, issues = [] } = audit.details ?? {};
|
|
2115
2160
|
const detailsValue = auditDetailsAuditValue(audit);
|
|
2116
|
-
if (issues.length === 0 &&
|
|
2117
|
-
return
|
|
2161
|
+
if (issues.length === 0 && !table2?.rows.length) {
|
|
2162
|
+
return new MarkdownDocument3().paragraph(detailsValue);
|
|
2118
2163
|
}
|
|
2119
|
-
const tableSectionContent =
|
|
2120
|
-
const issuesSectionContent = issues.length > 0
|
|
2121
|
-
return
|
|
2164
|
+
const tableSectionContent = table2 && tableSection(table2);
|
|
2165
|
+
const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues);
|
|
2166
|
+
return new MarkdownDocument3().details(
|
|
2122
2167
|
detailsValue,
|
|
2123
|
-
|
|
2168
|
+
new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
|
|
2124
2169
|
);
|
|
2125
2170
|
}
|
|
2126
2171
|
function auditsSection({
|
|
2127
2172
|
plugins
|
|
2128
2173
|
}) {
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2174
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
|
|
2175
|
+
plugins.flatMap(
|
|
2176
|
+
(plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
|
|
2177
|
+
),
|
|
2178
|
+
(doc, { plugin, ...audit }) => {
|
|
2179
|
+
const auditTitle = `${audit.title} (${plugin.title})`;
|
|
2135
2180
|
const detailsContent = auditDetails(audit);
|
|
2136
2181
|
const descriptionContent = metaDescription(audit);
|
|
2137
|
-
return
|
|
2138
|
-
}
|
|
2182
|
+
return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
|
|
2183
|
+
}
|
|
2139
2184
|
);
|
|
2140
|
-
return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
|
|
2141
2185
|
}
|
|
2142
2186
|
function aboutSection(report) {
|
|
2143
2187
|
const { date, plugins } = report;
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
{
|
|
2161
|
-
key: "plugin",
|
|
2162
|
-
align: "left"
|
|
2163
|
-
},
|
|
2164
|
-
{
|
|
2165
|
-
key: "audits"
|
|
2166
|
-
},
|
|
2167
|
-
{
|
|
2168
|
-
key: "version"
|
|
2169
|
-
},
|
|
2170
|
-
{
|
|
2171
|
-
key: "duration"
|
|
2172
|
-
}
|
|
2188
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "About").paragraph(
|
|
2189
|
+
md4`Report was created by ${md4.link(
|
|
2190
|
+
README_LINK,
|
|
2191
|
+
"Code PushUp"
|
|
2192
|
+
)} on ${formatDate(new Date(date))}.`
|
|
2193
|
+
).table(...pluginMetaTable({ plugins })).table(...reportMetaTable(report));
|
|
2194
|
+
}
|
|
2195
|
+
function pluginMetaTable({
|
|
2196
|
+
plugins
|
|
2197
|
+
}) {
|
|
2198
|
+
return [
|
|
2199
|
+
[
|
|
2200
|
+
{ heading: "Plugin", alignment: "left" },
|
|
2201
|
+
{ heading: "Audits", alignment: "center" },
|
|
2202
|
+
{ heading: "Version", alignment: "center" },
|
|
2203
|
+
{ heading: "Duration", alignment: "right" }
|
|
2173
2204
|
],
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
plugin: pluginTitle,
|
|
2182
|
-
audits: audits.length.toString(),
|
|
2183
|
-
version: codeMd(pluginVersion || ""),
|
|
2184
|
-
duration: formatDuration(pluginDuration)
|
|
2185
|
-
})
|
|
2186
|
-
)
|
|
2187
|
-
};
|
|
2205
|
+
plugins.map(({ title, audits, version = "", duration }) => [
|
|
2206
|
+
title,
|
|
2207
|
+
audits.length.toString(),
|
|
2208
|
+
version && md4.code(version),
|
|
2209
|
+
formatDuration(duration)
|
|
2210
|
+
])
|
|
2211
|
+
];
|
|
2188
2212
|
}
|
|
2189
|
-
function
|
|
2213
|
+
function reportMetaTable({
|
|
2190
2214
|
commit,
|
|
2191
2215
|
version,
|
|
2192
2216
|
duration,
|
|
2193
2217
|
plugins,
|
|
2194
2218
|
categories
|
|
2195
2219
|
}) {
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
{
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
},
|
|
2203
|
-
{
|
|
2204
|
-
key: "version"
|
|
2205
|
-
},
|
|
2206
|
-
{
|
|
2207
|
-
key: "duration"
|
|
2208
|
-
},
|
|
2209
|
-
{
|
|
2210
|
-
key: "plugins"
|
|
2211
|
-
},
|
|
2212
|
-
{
|
|
2213
|
-
key: "categories"
|
|
2214
|
-
},
|
|
2215
|
-
{
|
|
2216
|
-
key: "audits"
|
|
2217
|
-
}
|
|
2220
|
+
return [
|
|
2221
|
+
[
|
|
2222
|
+
{ heading: "Commit", alignment: "left" },
|
|
2223
|
+
{ heading: "Version", alignment: "center" },
|
|
2224
|
+
{ heading: "Duration", alignment: "right" },
|
|
2225
|
+
{ heading: "Plugins", alignment: "center" },
|
|
2226
|
+
{ heading: "Categories", alignment: "center" },
|
|
2227
|
+
{ heading: "Audits", alignment: "center" }
|
|
2218
2228
|
],
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
commit:
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
plugins
|
|
2225
|
-
categories
|
|
2226
|
-
|
|
2227
|
-
|
|
2229
|
+
[
|
|
2230
|
+
[
|
|
2231
|
+
commit ? `${commit.message} (${commit.hash})` : "N/A",
|
|
2232
|
+
md4.code(version),
|
|
2233
|
+
formatDuration(duration),
|
|
2234
|
+
plugins.length.toString(),
|
|
2235
|
+
categories.length.toString(),
|
|
2236
|
+
plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
|
|
2237
|
+
]
|
|
2228
2238
|
]
|
|
2229
|
-
|
|
2239
|
+
];
|
|
2230
2240
|
}
|
|
2231
2241
|
|
|
2232
2242
|
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
link: link7,
|
|
2238
|
-
bold: boldMd3,
|
|
2239
|
-
italic: italicMd,
|
|
2240
|
-
table: table4,
|
|
2241
|
-
section: section5
|
|
2242
|
-
} = md;
|
|
2243
|
-
var { details: details3 } = html;
|
|
2243
|
+
import {
|
|
2244
|
+
MarkdownDocument as MarkdownDocument4,
|
|
2245
|
+
md as md5
|
|
2246
|
+
} from "build-md";
|
|
2244
2247
|
var MAX_ROWS = 100;
|
|
2245
|
-
function generateMdReportsDiff(diff) {
|
|
2246
|
-
return
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
);
|
|
2252
|
-
}
|
|
2253
|
-
function
|
|
2248
|
+
function generateMdReportsDiff(diff, portalUrl) {
|
|
2249
|
+
return new MarkdownDocument4().$concat(
|
|
2250
|
+
createDiffHeaderSection(diff, portalUrl),
|
|
2251
|
+
createDiffCategoriesSection(diff),
|
|
2252
|
+
createDiffGroupsSection(diff),
|
|
2253
|
+
createDiffAuditsSection(diff)
|
|
2254
|
+
).toString();
|
|
2255
|
+
}
|
|
2256
|
+
function createDiffHeaderSection(diff, portalUrl) {
|
|
2254
2257
|
const outcomeTexts = {
|
|
2255
|
-
positive:
|
|
2256
|
-
negative:
|
|
2257
|
-
mixed:
|
|
2258
|
+
positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
|
|
2259
|
+
negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
|
|
2260
|
+
mixed: md5`🤨 Code PushUp report has both ${md5.bold(
|
|
2258
2261
|
"improvements and regressions"
|
|
2259
2262
|
)}`,
|
|
2260
|
-
unchanged:
|
|
2263
|
+
unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
|
|
2261
2264
|
};
|
|
2262
2265
|
const outcome = mergeDiffOutcomes(
|
|
2263
2266
|
changesToDiffOutcomes([
|
|
@@ -2267,143 +2270,127 @@ function formatDiffHeaderSection(diff) {
|
|
|
2267
2270
|
])
|
|
2268
2271
|
);
|
|
2269
2272
|
const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
2270
|
-
return
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_1, "Code PushUp").paragraph(
|
|
2274
|
+
diff.commits ? md5`${outcomeTexts[outcome]} – ${styleCommits(diff.commits)}.` : outcomeTexts[outcome]
|
|
2275
|
+
).paragraph(
|
|
2276
|
+
portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}")
|
|
2273
2277
|
);
|
|
2274
2278
|
}
|
|
2275
|
-
function
|
|
2279
|
+
function createDiffCategoriesSection(diff) {
|
|
2276
2280
|
const { changed, unchanged, added } = diff.categories;
|
|
2277
2281
|
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2278
2282
|
const hasChanges = unchanged.length < categoriesCount;
|
|
2279
2283
|
if (categoriesCount === 0) {
|
|
2280
|
-
return
|
|
2284
|
+
return null;
|
|
2281
2285
|
}
|
|
2282
2286
|
const columns = [
|
|
2283
|
-
{
|
|
2284
|
-
{
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
+
{ heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
|
|
2288
|
+
{
|
|
2289
|
+
heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
|
|
2290
|
+
alignment: "center"
|
|
2291
|
+
},
|
|
2292
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2293
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2287
2294
|
];
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
(row) => hasChanges ? row : { category: row.category, after: row.after }
|
|
2315
|
-
)
|
|
2316
|
-
}),
|
|
2317
|
-
added.length > 0 && section5(italicMd("(\\*) New category."))
|
|
2318
|
-
);
|
|
2295
|
+
const rows = [
|
|
2296
|
+
...sortChanges(changed).map((category) => [
|
|
2297
|
+
formatTitle(category),
|
|
2298
|
+
formatScoreWithColor(category.scores.before, {
|
|
2299
|
+
skipBold: true
|
|
2300
|
+
}),
|
|
2301
|
+
formatScoreWithColor(category.scores.after),
|
|
2302
|
+
formatScoreChange(category.scores.diff)
|
|
2303
|
+
]),
|
|
2304
|
+
...added.map((category) => [
|
|
2305
|
+
formatTitle(category),
|
|
2306
|
+
md5.italic("n/a (\\*)"),
|
|
2307
|
+
formatScoreWithColor(category.score),
|
|
2308
|
+
md5.italic("n/a (\\*)")
|
|
2309
|
+
]),
|
|
2310
|
+
...unchanged.map((category) => [
|
|
2311
|
+
formatTitle(category),
|
|
2312
|
+
formatScoreWithColor(category.score, { skipBold: true }),
|
|
2313
|
+
formatScoreWithColor(category.score),
|
|
2314
|
+
"\u2013"
|
|
2315
|
+
])
|
|
2316
|
+
];
|
|
2317
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F3F7}\uFE0F Categories").table(
|
|
2318
|
+
hasChanges ? columns : columns.slice(0, 2),
|
|
2319
|
+
rows.map((row) => hasChanges ? row : row.slice(0, 2))
|
|
2320
|
+
).paragraph(added.length > 0 && md5.italic("(\\*) New category."));
|
|
2319
2321
|
}
|
|
2320
|
-
function
|
|
2322
|
+
function createDiffGroupsSection(diff) {
|
|
2321
2323
|
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2322
|
-
return
|
|
2323
|
-
}
|
|
2324
|
-
return
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
{
|
|
2330
|
-
{
|
|
2331
|
-
{
|
|
2332
|
-
{
|
|
2324
|
+
return null;
|
|
2325
|
+
}
|
|
2326
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F5C3}\uFE0F Groups").$concat(
|
|
2327
|
+
createGroupsOrAuditsDetails(
|
|
2328
|
+
"group",
|
|
2329
|
+
diff.groups,
|
|
2330
|
+
[
|
|
2331
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2332
|
+
{ heading: "\u{1F5C3}\uFE0F Group", alignment: "left" },
|
|
2333
|
+
{ heading: "\u2B50 Previous score", alignment: "center" },
|
|
2334
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
2335
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2333
2336
|
],
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2337
|
+
sortChanges(diff.groups.changed).map((group) => [
|
|
2338
|
+
formatTitle(group.plugin),
|
|
2339
|
+
formatTitle(group),
|
|
2340
|
+
formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
2341
|
+
formatScoreWithColor(group.scores.after),
|
|
2342
|
+
formatScoreChange(group.scores.diff)
|
|
2343
|
+
])
|
|
2344
|
+
)
|
|
2342
2345
|
);
|
|
2343
2346
|
}
|
|
2344
|
-
function
|
|
2345
|
-
return
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
{
|
|
2351
|
-
{
|
|
2352
|
-
{
|
|
2353
|
-
{
|
|
2347
|
+
function createDiffAuditsSection(diff) {
|
|
2348
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$concat(
|
|
2349
|
+
createGroupsOrAuditsDetails(
|
|
2350
|
+
"audit",
|
|
2351
|
+
diff.audits,
|
|
2352
|
+
[
|
|
2353
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
2354
|
+
{ heading: "\u{1F6E1}\uFE0F Audit", alignment: "left" },
|
|
2355
|
+
{ heading: "\u{1F4CF} Previous value", alignment: "center" },
|
|
2356
|
+
{ heading: "\u{1F4CF} Current value", alignment: "center" },
|
|
2357
|
+
{ heading: "\u{1F504} Value change", alignment: "center" }
|
|
2354
2358
|
],
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
+
sortChanges(diff.audits.changed).map((audit) => [
|
|
2360
|
+
formatTitle(audit.plugin),
|
|
2361
|
+
formatTitle(audit),
|
|
2362
|
+
`${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
2363
|
+
md5`${scoreMarker(audit.scores.after, "square")} ${md5.bold(
|
|
2359
2364
|
audit.displayValues.after || audit.values.after.toString()
|
|
2360
2365
|
)}`,
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
})
|
|
2366
|
+
formatValueChange(audit)
|
|
2367
|
+
])
|
|
2368
|
+
)
|
|
2365
2369
|
);
|
|
2366
2370
|
}
|
|
2367
|
-
function
|
|
2368
|
-
|
|
2371
|
+
function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
|
|
2372
|
+
if (changed.length === 0) {
|
|
2373
|
+
return new MarkdownDocument4().paragraph(
|
|
2374
|
+
summarizeUnchanged(token, { changed, unchanged })
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return new MarkdownDocument4().details(
|
|
2369
2378
|
summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
...tableData,
|
|
2373
|
-
rows: tableData.rows.slice(0, MAX_ROWS)
|
|
2374
|
-
// use never to avoid typing problem
|
|
2375
|
-
}),
|
|
2376
|
-
changed.length > MAX_ROWS && italicMd(
|
|
2379
|
+
md5`${md5.table(columns, rows.slice(0, MAX_ROWS))}${changed.length > MAX_ROWS ? md5.paragraph(
|
|
2380
|
+
md5.italic(
|
|
2377
2381
|
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
2378
2382
|
token
|
|
2379
2383
|
)} are listed above for brevity.`
|
|
2380
|
-
)
|
|
2381
|
-
|
|
2382
|
-
)
|
|
2384
|
+
)
|
|
2385
|
+
) : ""}${unchanged.length > 0 ? md5.paragraph(summarizeUnchanged(token, { changed, unchanged })) : ""}`
|
|
2383
2386
|
);
|
|
2384
2387
|
}
|
|
2385
|
-
function formatScoreChange(diff) {
|
|
2386
|
-
const marker = getDiffMarker(diff);
|
|
2387
|
-
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
2388
|
-
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
2389
|
-
}
|
|
2390
|
-
function formatValueChange({
|
|
2391
|
-
values,
|
|
2392
|
-
scores
|
|
2393
|
-
}) {
|
|
2394
|
-
const marker = getDiffMarker(values.diff);
|
|
2395
|
-
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
2396
|
-
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
2397
|
-
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
2398
|
-
}
|
|
2399
2388
|
function summarizeUnchanged(token, { changed, unchanged }) {
|
|
2400
|
-
return
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
].join(" ")
|
|
2406
|
-
);
|
|
2389
|
+
return [
|
|
2390
|
+
changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
|
|
2391
|
+
unchanged.length === 1 ? "is" : "are",
|
|
2392
|
+
"unchanged."
|
|
2393
|
+
].join(" ");
|
|
2407
2394
|
}
|
|
2408
2395
|
function summarizeDiffOutcomes(outcomes, token) {
|
|
2409
2396
|
return objectToEntries(countDiffOutcomes(outcomes)).filter(
|
|
@@ -2428,7 +2415,7 @@ function formatTitle({
|
|
|
2428
2415
|
docsUrl
|
|
2429
2416
|
}) {
|
|
2430
2417
|
if (docsUrl) {
|
|
2431
|
-
return
|
|
2418
|
+
return md5.link(docsUrl, title);
|
|
2432
2419
|
}
|
|
2433
2420
|
return title;
|
|
2434
2421
|
}
|
|
@@ -2472,8 +2459,22 @@ function countDiffOutcomes(outcomes) {
|
|
|
2472
2459
|
};
|
|
2473
2460
|
}
|
|
2474
2461
|
|
|
2462
|
+
// packages/utils/src/lib/reports/load-report.ts
|
|
2463
|
+
import { join as join3 } from "node:path";
|
|
2464
|
+
async function loadReport(options) {
|
|
2465
|
+
const { outputDir, filename, format } = options;
|
|
2466
|
+
await ensureDirectoryExists(outputDir);
|
|
2467
|
+
const filePath = join3(outputDir, `${filename}.${format}`);
|
|
2468
|
+
if (format === "json") {
|
|
2469
|
+
const content = await readJsonFile(filePath);
|
|
2470
|
+
return reportSchema.parse(content);
|
|
2471
|
+
}
|
|
2472
|
+
const text = await readTextFile(filePath);
|
|
2473
|
+
return text;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2475
2476
|
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
2476
|
-
import
|
|
2477
|
+
import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
|
|
2477
2478
|
function log(msg = "") {
|
|
2478
2479
|
ui().logger.log(msg);
|
|
2479
2480
|
}
|
|
@@ -2490,14 +2491,14 @@ function logStdoutSummary(report) {
|
|
|
2490
2491
|
}
|
|
2491
2492
|
function reportToHeaderSection(report) {
|
|
2492
2493
|
const { packageName, version } = report;
|
|
2493
|
-
return `${
|
|
2494
|
+
return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version}`;
|
|
2494
2495
|
}
|
|
2495
2496
|
function logPlugins(report) {
|
|
2496
2497
|
const { plugins } = report;
|
|
2497
2498
|
plugins.forEach((plugin) => {
|
|
2498
2499
|
const { title, audits } = plugin;
|
|
2499
2500
|
log();
|
|
2500
|
-
log(
|
|
2501
|
+
log(bold4.magentaBright(`${title} audits`));
|
|
2501
2502
|
log();
|
|
2502
2503
|
audits.forEach((audit) => {
|
|
2503
2504
|
ui().row([
|
|
@@ -2512,7 +2513,7 @@ function logPlugins(report) {
|
|
|
2512
2513
|
padding: [0, 3, 0, 0]
|
|
2513
2514
|
},
|
|
2514
2515
|
{
|
|
2515
|
-
text:
|
|
2516
|
+
text: cyanBright(audit.displayValue || `${audit.value}`),
|
|
2516
2517
|
width: 10,
|
|
2517
2518
|
padding: [0, 0, 0, 0]
|
|
2518
2519
|
}
|
|
@@ -2523,42 +2524,38 @@ function logPlugins(report) {
|
|
|
2523
2524
|
}
|
|
2524
2525
|
function logCategories({ categories, plugins }) {
|
|
2525
2526
|
const hAlign = (idx) => idx === 0 ? "left" : "right";
|
|
2526
|
-
const rows = categories.map(({ title, score, refs }) => [
|
|
2527
|
+
const rows = categories.map(({ title, score, refs, isBinary }) => [
|
|
2527
2528
|
title,
|
|
2528
|
-
applyScoreColor({ score })
|
|
2529
|
+
`${binaryIconPrefix(score, isBinary)}${applyScoreColor({ score })}`,
|
|
2529
2530
|
countCategoryAudits(refs, plugins)
|
|
2530
2531
|
]);
|
|
2531
|
-
const
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
content:
|
|
2532
|
+
const table2 = ui().table();
|
|
2533
|
+
table2.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
|
|
2534
|
+
table2.head(
|
|
2535
|
+
REPORT_RAW_OVERVIEW_TABLE_HEADERS.map((heading, idx) => ({
|
|
2536
|
+
content: cyan(heading),
|
|
2536
2537
|
hAlign: hAlign(idx)
|
|
2537
2538
|
}))
|
|
2538
2539
|
);
|
|
2539
2540
|
rows.forEach(
|
|
2540
|
-
(row) =>
|
|
2541
|
+
(row) => table2.row(
|
|
2541
2542
|
row.map((content, idx) => ({
|
|
2542
2543
|
content: content.toString(),
|
|
2543
2544
|
hAlign: hAlign(idx)
|
|
2544
2545
|
}))
|
|
2545
2546
|
)
|
|
2546
2547
|
);
|
|
2547
|
-
log(
|
|
2548
|
+
log(bold4.magentaBright("Categories"));
|
|
2548
2549
|
log();
|
|
2549
|
-
|
|
2550
|
+
table2.render();
|
|
2550
2551
|
log();
|
|
2551
2552
|
}
|
|
2552
|
-
function
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
}
|
|
2558
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
2559
|
-
return style.yellow(formattedScore);
|
|
2560
|
-
}
|
|
2561
|
-
return style.red(formattedScore);
|
|
2553
|
+
function binaryIconPrefix(score, isBinary) {
|
|
2554
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, {
|
|
2555
|
+
passIcon: bold4(green2("\u2713")),
|
|
2556
|
+
failIcon: bold4(red("\u2717")),
|
|
2557
|
+
postfix: " "
|
|
2558
|
+
});
|
|
2562
2559
|
}
|
|
2563
2560
|
|
|
2564
2561
|
// packages/utils/src/lib/reports/scoring.ts
|
|
@@ -2648,56 +2645,6 @@ function parseScoringParameters(refs, scoreFn) {
|
|
|
2648
2645
|
return scoredRefs;
|
|
2649
2646
|
}
|
|
2650
2647
|
|
|
2651
|
-
// packages/utils/src/lib/reports/sorting.ts
|
|
2652
|
-
function sortReport(report) {
|
|
2653
|
-
const { categories, plugins } = report;
|
|
2654
|
-
const sortedCategories = categories.map((category) => {
|
|
2655
|
-
const { audits, groups } = category.refs.reduce(
|
|
2656
|
-
(acc, ref) => ({
|
|
2657
|
-
...acc,
|
|
2658
|
-
...ref.type === "group" ? {
|
|
2659
|
-
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
2660
|
-
} : {
|
|
2661
|
-
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
2662
|
-
}
|
|
2663
|
-
}),
|
|
2664
|
-
{ groups: [], audits: [] }
|
|
2665
|
-
);
|
|
2666
|
-
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
2667
|
-
compareCategoryAuditsAndGroups
|
|
2668
|
-
);
|
|
2669
|
-
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
2670
|
-
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
2671
|
-
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
2672
|
-
);
|
|
2673
|
-
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
2674
|
-
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
2675
|
-
);
|
|
2676
|
-
return aIndex - bIndex;
|
|
2677
|
-
});
|
|
2678
|
-
return { ...category, refs: sortedRefs };
|
|
2679
|
-
});
|
|
2680
|
-
return {
|
|
2681
|
-
...report,
|
|
2682
|
-
categories: sortedCategories,
|
|
2683
|
-
plugins: sortPlugins(plugins)
|
|
2684
|
-
};
|
|
2685
|
-
}
|
|
2686
|
-
function sortPlugins(plugins) {
|
|
2687
|
-
return plugins.map((plugin) => ({
|
|
2688
|
-
...plugin,
|
|
2689
|
-
audits: [...plugin.audits].sort(compareAudits).map(
|
|
2690
|
-
(audit) => audit.details?.issues ? {
|
|
2691
|
-
...audit,
|
|
2692
|
-
details: {
|
|
2693
|
-
...audit.details,
|
|
2694
|
-
issues: [...audit.details.issues].sort(compareIssues)
|
|
2695
|
-
}
|
|
2696
|
-
} : audit
|
|
2697
|
-
)
|
|
2698
|
-
}));
|
|
2699
|
-
}
|
|
2700
|
-
|
|
2701
2648
|
// packages/utils/src/lib/verbose-utils.ts
|
|
2702
2649
|
function getLogVerbose(verbose = false) {
|
|
2703
2650
|
return (msg) => {
|
|
@@ -2720,13 +2667,13 @@ var verboseUtils = (verbose = false) => ({
|
|
|
2720
2667
|
export {
|
|
2721
2668
|
CODE_PUSHUP_DOMAIN,
|
|
2722
2669
|
FOOTER_PREFIX,
|
|
2670
|
+
HIERARCHY,
|
|
2723
2671
|
NEW_LINE,
|
|
2724
2672
|
ProcessError,
|
|
2725
2673
|
README_LINK,
|
|
2726
2674
|
SPACE,
|
|
2727
2675
|
TAB,
|
|
2728
2676
|
TERMINAL_WIDTH,
|
|
2729
|
-
apostrophize,
|
|
2730
2677
|
calcDuration,
|
|
2731
2678
|
capitalize,
|
|
2732
2679
|
compareIssueSeverity,
|
|
@@ -2771,7 +2718,7 @@ export {
|
|
|
2771
2718
|
logMultipleResults,
|
|
2772
2719
|
logStdoutSummary,
|
|
2773
2720
|
matchArrayItemsByKey,
|
|
2774
|
-
|
|
2721
|
+
mergeConfigs,
|
|
2775
2722
|
normalizeSemver,
|
|
2776
2723
|
objectFromEntries,
|
|
2777
2724
|
objectToCliArgs,
|